複数列をもつTreeViewについて

以前の記事でも書いた複数列を持つTreeViewについて、独自でいろいろと改良したコントロールをdllにして、プロジェクトに組み込んでいたが、dllでの利用がやはりなれないので、シンプルにしたものを作成した。(MultiColumnTreeView)
以前の記事:https://inakaprogrammer.blogspot.com/2017/10/treelistview.html

(参照元:https://www.codeproject.com/Articles/24973/TreeListView)




まずは展開・非展開のToggleButtonをよく見る「+」「-」へ変更している。
あとは、カーソルのあるテキストボックスの背景色を変更するために、独自コントロールを作成し、DataTemplateとして指定できるようにしている。状況によってはコンボボックスやDatePickerなどを設置できるようにするべきかもしれない。

別プロジェクトとしてdllを作成するほどではないので、下記のサンプルプロジェクトの中では、TreeListViewフォルダをプロジェクト内に作成し、そのフォルダを別プロジェクトに移して、名前空間を修正したうえで、App.xamlに下記のソースを追加すれば使えるようになるはず。

<Application x:Class="MultiColumnTreeView.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MultiColumnTreeView"
             StartupUri="MainWindow.xaml">
    <Application.Resources>

        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="TreeListView/TreeListViewStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>

    </Application.Resources>
</Application>


各画面での利用例

       <TreeListView:TreeListView x:Name="treeView" Background="White" AllowsColumnReorder="True" ItemsSource="{Binding TreeRoot}" >
            <TreeListView:TreeListView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}" />
            </TreeListView:TreeListView.ItemTemplate>

            <TreeListView:TreeListView.Columns>

                <GridViewColumn Header="分類" Width="200">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <!-- Marginで左右から-6pxとしないとHearder部のタイトルとラインの位置がずれる.GridViewの既知の不具合 -->
                            <Border BorderBrush="Black"  BorderThickness="0,0,0.5,0" Margin="-6,0,-6,0">
                                <DockPanel   >
                                    <!--TreeListViewExpanderは必ず最初の列に設置-->
                                    <TreeListView:TreeListViewExpander Focusable="False"/>
                                    <TreeListViewSubControls:TreeListItemTextBox Text="{Binding Text}" VerticalContentAlignment="Center" VerticalAlignment="Stretch" Margin="2,2,2,2"
                                                                  HorizontalAlignment="Stretch" BorderThickness="0" Opacity="1"  TabIndex="1" />
                                </DockPanel>
                            </Border>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>

                <GridViewColumn Header="期限" Width="80">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <!-- Marginで左右から-6pxとしないとHearder部のタイトルとラインの位置がずれる.GridViewの既知の不具合 -->
                            <Border BorderBrush="Black"  BorderThickness="0,0,0.5,0" Margin="-6,0,-6,0">
                                <DockPanel   >
                                    <TreeListViewSubControls:TreeListItemTextBox Text="{Binding Limit}" VerticalContentAlignment="Center" VerticalAlignment="Stretch" Margin="2,2,2,2"
                                                                  HorizontalAlignment="Stretch"  HorizontalContentAlignment="Center" BorderThickness="0" Opacity="1"  TabIndex="1" />
                                </DockPanel>
                            </Border>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>

            </TreeListView:TreeListView.Columns>
        </TreeListView:TreeListView>


サンプルプロジェクト
<DownLoad>


ItemsTemplateで自動生成されたコントロールの取得について

メイン画面にListViewを配置し、ItemsTemplateにListBoxを設定している。
ListViewには ObservableCollection<ListBoxViewModel>をバインドしている。
コメj理宇市ListBoxViewModelクラスは独自に作成したListBoxにバインドするためのViewModel

この状態で、ListBoxの項目を上下に移動させた場合に、移動させた項目の選択状態がなくなってしまうので、連続で上下に移動させることができない。

 <上下移動させたときの処理>
     int i = cardlist.Cards.IndexOf(card);
     if (i == 0) { break; }
     model.Card c = cardlist.Cards[i - 1];
     cardlist.Cards.Remove(card);
     cardlist.Cards.Insert(i-1, card);
     cardlist.SetSelectedIndex(i - 1);

そのため、ListViewから自動生成されたListBoxを取得し、SelectedIndexを再設定する方法を調べたところ、VisualTreeHelperを利用した素敵なメソッドを作られた方がいたので、なんとか実装できた。
(https://blog.xin9le.net/entry/2013/10/29/222336)
※クラス名を「DependencyObjectExtensions」から「VisualTreeHelperExtention」へ変更して利用している













始めはViewModelからViewを取得して、そこからコントロールを辿って・・・ということを考えていろいろと調べていたが、ViewModelからViewを取得するのはなかなかめんどくさそうだった。そもそもそんな必要なかったんだけど・・・。


viewModel.CardLists・・・ListViewにバインドしているObservableCollection<ListBoxViewModel>
ListBoxViewModel・・・ListBoxにバインドしているViewModelObservableCollection<CustomListBoxItem>

foreach (var cardlist in viewModel.CardLists)
{
    if (cardlist.Cards.Contains(card)) {
        // 入れ替え
        int i = cardlist.Cards.IndexOf(card);
        if (i == 0) { break; }
        model.Card c = cardlist.Cards[i - 1];
        cardlist.Cards.Remove(card);
        cardlist.Cards.Insert(i-1, card);
        cardlist.SetSelectedIndex(i - 1);

        // ListViewからVisualTreeHerlerを利用してCardListコントロールを取得し、SelectedIndexを設定する
        var controlCardLists = VisualTreeHelperExtention.Descendants<controls.CardList>(lstMainCardList);
        foreach (var ctlCardList in controlCardLists)
        {
            if (ctlCardList.GetBindingViewModel() == cardlist) {
                ctlCardList.GetMainList().SelectedIndex = ctlCardList.GetMainList().Items.IndexOf(card); break;
            }
        }
    };
}

IValueConverterについて

IValueConverterについて、文字列をバインドしてIValueConverterで色に変換したものをバインドすることがあったのでメモ。

図のように色を変更するような場合に利用する。








色の情報は文字列や数値として保持し、バインドするときにSolidColorBrushに変換してからバインドするような場合に利用する。

CardBackgroundConverter.cs
   public class CardBackgroundConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string colorCode = (string)value;
            SolidColorBrush color =null;

            if(colorCode == "") { return null; }

            var alpha = System.Convert.ToByte(System.Convert.ToInt32(colorCode.Substring(1, 2), 16));
            var red = System.Convert.ToByte(System.Convert.ToInt32(colorCode.Substring(3, 2), 16));
            var green = System.Convert.ToByte(System.Convert.ToInt32(colorCode.Substring(5, 2), 16));
            var blue = System.Convert.ToByte(System.Convert.ToInt32(colorCode.Substring(7, 2), 16));

            color = new SolidColorBrush(Color.FromArgb(alpha, red, green, blue));

            return color;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

使い方(一部抜粋)
<UserControl x:Class="TaskManager.controls.Card"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:TaskManager.controls"
             xmlns:converter="clr-namespace:TaskManager.converter"
             mc:Ignorable="d"
             Height="Auto" Width="250">
    <UserControl.Resources>
        <converter:CardBackgroundConverter x:Key="BackgroundConverter"/>
    </UserControl.Resources>

    <DockPanel Background="{Binding ColorCode,Converter={StaticResource BackgroundConverter}}"/>

</UserControl>

仕様変更などで色の種類が増えた場合にも、文字色の選択肢を増やすだけで対応が可能。
色以外にもVisibilityなどもIValueConverterで変換できる。
例)ある選択肢の値が1,2,5の時は別のコントロールを非表示。それ以外は表示など

正直慣れないうちは何をしているのかよくわからない。
慣れてくると段々、見るポイントが分かってくる。
デザイナをみてバインド時にValueConverterを指定していれば何の値(バインドされているプロパティの値)がそのコントロールの何のプロパティを変更しているかわかる、気がする。

TaskManagerについて

タスクを管理するためのお試しソフトをVectorに登録してみた。
テーブルレイアウトをA5:SQL mk2というフリーソフトで作成して、データベースはsqliteを利用している。

https://www.vector.co.jp/soft/screen/winnt/util/se518706.html

まだバグの修正をしなければならないところではあるけれど、自分で利用する分には十分使えそう。
まぁこの手のソフトはフリーでもっといいのがあるけど、ログインアカウントを登録しなければならなかったりするWeb系統のが多くて、おっさんにはなんとなく合わない。

ずっとWindowsフォームでアプリを作ってきたせいか、Webアプリでの入力とか登録にいまいち慣れなくて、ついついオフラインで利用できるソフトを探してしまう。

自分で作っちゃえば、気になるところを直したり、めんどくさいから妥協したりするのもやり放題。データもテーブル構造を把握しているから抜きたい放題。
まぁそんな複雑なテーブルではないんだけど・・・。
パスワードも設定してないから、データを直接いじろうと思えばちょっと知識があればいくらでもいじれる。

ガントチャート作成日記

あいかわらずのんびりと作成中。
タスクの削除を付けたが、チャートの削除をまだつけていないことに気づいた。
っていうかそれ以前にデータの保存処理を付けてないし、初期の日付範囲とかも一切つけていない。



プロジェクトの初期設定として、プロジェクトの開始日・終了日を変更できるようにすることから始めるべきかな・・・。

頑張ったところとしては、左側のタスクの一番左側のタスクの縦幅を広げて、大きいグループの範囲を広げた時に、濃い線をタスクの一覧とガントチャートの一覧に表示して、どこまでがグループに属しているかをわかりやすくしたことと、マウスで複数のチャートを選択・移動できるようにした。

グループを表現する線についてはGanttGridTemplateSelecter.csとGanttGridTaskViewTemplateSelecter.csにパターンを追加して、DataTemplateを新しく定義してこちゃこちゃすればできるけど、マウスでの移動については一苦労した。
CanvasコントロールをGridコントロールの下に張り付けて、Rectを新たに描画している。

Canvasコントロールを追加したことでスクロールバーがうまく出なくなってしまったので、SizeChangeイベントで変更するようにした。

マウスの範囲選択でGridコントロール上のチャートを選択するのもネットで調べてみるといろいろとサンプルがある。
世の中にはすごい人がいるなぁ。

<ソース>
        #region 右側のCanvasのサイズを合わせる

        // マウスで複数選択をさせるためにCanvasコントロールを利用しているがCanvasコントロールはScrollBarが出しずらい
        // ガントチャートの上にCanvasをTransportで重ねることも考えたが、チャートをクリックできなくなってしまう。
        // Canvasコントロールをクリックしたときの通知などをチャート側に伝えるようにすればよいが、さすがに手間なので
        // Canvasコントロールのサイズを変更しScrollBarが出るようにした。

        /// <summary>
        /// 幅を合わせる
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void gridCalender_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Grid grid = (Grid)sender;
            CalenderBaseCanvas.Width = e.NewSize.Width;
        }

        /// <summary>
        /// 高さはタスク一覧側と同じになるので合わせる
        /// gridCalender側と合わせるとタスクが少ないときにスクロールバーが上に出てしまう
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void icTaskGrid_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            CalenderBaseCanvas.Height = icTaskGrid.ActualHeight;
        }
        #endregion

<DownLoad>

ガントチャート作成日記

久しぶりの更新。
ガントチャートを自分で自由に作成できるツールを作ってみているが、いい感じになってきた。(保存はできないけど・・・。)

左側に一覧を作って、自分で縦幅を自由に変えて、グループを表現しつつ、右側にチャートやメモを配置していく。

スマホのゲームをしながらだからいつになるかわからないけど・・・。


試行錯誤をしながらだが、根本は変わらない。Gridコントロールにコントロールを配置していくだけ。DataTemplateSelecterでDataTemplateを切り替えてコントロールを切り替えていく。慣れてくると簡単に表現できるようになってくるので楽しい。
Paintイベントで頑張る必要もないので作りやすい。
ただし、データ数が多くなると速度が気になりそう・・・。

まぁ趣味だしいいよね。

<DownLoad>

ガントチャート作成日記

久しぶりに更新。スマホのゲームにはまってしまって、なかなかこっちに時間が割けなかった。
とりあえず、DataGridではなく、独自で一覧を作成する方向で作り続けてみようと思う。

チャートの部分と同様にGridコントロールに独自クラスをバインドする形式にして、DataTemplateSelecterでDataTemplateを切り替えることでテキストボックスや行部分を表示している。
Thumbコントロールを使ってリサイズも可能なようにしておいて、セルのマージができないのを補ってみた。

工夫したところとしては処理速度を補うために、バインド用のリストとテキストボックス用のリストと行を表現するためのリストを分けた。

<ソース抜粋(GanntGridViewModel.cs)>
   // 左部のタスク一覧を表現するためのセルを格納する各リスト
   this.GanttGridTaskViewPartsRowCells = new ObservableCollection<GanttCell>();
   this.GanttGridTaskViewPartsTaskCells = new ObservableCollection<GanttCell>();
   this.GanttGridTaskViewCells = new ObservableCollection<GanttCell>();

GanttGridTaskViewPartsRowCellsとGanttGridTaskViewPartsTaskCellsにGanntCellクラスをインスタンス化してそれぞれに追加したのちに、this.GanttGridTaskViewCellsに両方のリストの内容をAddしてバインディングに利用する。
そうすることでタスクだけを変更したい時にはGanttGridTaskViewCellsをループするのではなく、GanttGridTaskViewPartsTaskCellsをループすることで対応できる。

問題としては、タスクを追加したときにリストへの追加ミスがないようにしないといけないところと、その際に他の問題が起きないか、というところ・・・。

これは試してみないとわからないので、次回やってみようと思う。

<DownLoad>

PowerShellでWPFアプリケーションをBindingするときの注意点

 1.PowerShellでWPFアプリケーションのBindingについて マニアックだけどPowerShellでWPFアプリケーションを作っている。INotifyChangedを実装したいけど、PowerShellにはgetterやsetterがないので通知が発行できない。 そ...