WPFのお勉強

WPFで何らかのシステムを組む際のサンプルがあまりないので自分でいろいろ勉強してみる。

とりあえずログイン画面を作ってみた。


正直、販売管理システムやワークフローシステムなどを作るのにWPFが適しているかいまだに疑問ではある。
個人的には作ってみたいが、Windowsフォームのほうが安定しているし、メンバーも集めやすいし、アニメーションにこだわる必要など皆無。カーソルの動きやフォーカスの当て方などをいろいろ用途によって変える必要があるのでMVVMの分離も一癖ある。
特殊な独自コントロールもほとんど必要ない。
趣味でやるのならこだわってみてもいいが、原価やコストなどを考えるとWPFでやるメリットを見いだせない。

とかいろいろ考えているとお勉強にならないのでとりあえず趣味の範囲で組んでみようと思う。

まずコントロールの配置のためのパネルをどうするかで悩んだ。
Gridで綺麗に分けることができればいいが、項目一つ一つにGridの列や行を定義するのはめんどくさい。項目が増えた時に変更箇所も多い。

ということで、Gridをベースとしてその上にCanvasコントロールを配置することにした。

Login.xaml

<Window x:Class="SystemSample.win00_0000"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SystemSample"
        xmlns:control="clr-namespace:SystemSample.Controls"
        mc:Ignorable="d"
        Title="ログイン画面" Height="250" Width="450" Style="{StaticResource LoginWindowStyle}" WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="5"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="5"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="5"/>
        </Grid.RowDefinitions>

        <Canvas Grid.Column="1" Grid.Row="1">
            <Label x:Name="lblTitle" Content="販売管理システム" Canvas.Left="10" Canvas.Top="10" Width="412" FontSize="32" FontFamily="Arphic Kaisho4JIS" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" />
            <control:CsTextBox x:Name="txtUserID" Canvas.Left="167" Text="{Binding UserID}"   Canvas.Top="79"  Width="205" Style="{StaticResource NormalTextBox}"/>
            <control:CsTextBox x:Name="txtPass"   Canvas.Left="167" Text="{Binding PassWord}" Canvas.Top="108" Width="205" Style="{StaticResource NormalTextBox}"/>
            <Label x:Name="lblUserID" Content="ユーザーID" Canvas.Left="60" Canvas.Top="79" Width="105" Style="{StaticResource RequiredLabel}"/>
            <Label x:Name="lblPass"   Content="パスワード" Canvas.Left="60" Canvas.Top="108" Width="105" Style="{StaticResource RequiredLabel}" />
            <Button x:Name="btnLogin" Content="ログイン" Canvas.Left="280" Canvas.Top="159" Width="95" Style="{StaticResource NormalButton}" />
        </Canvas>

    </Grid>
</Window>

CsTextBoxはTextBoxを継承しただけの独自コントロール。
まだ何の制御も実装していない。
本当は数値入力のみとか半角英数字のみ入力できるようにするなど実装したいがそれはそのうちということで。

StaticResourceについてはプロジェクト内でスタイルを共通化したいので、App.xamlに定義を行っている。

・App.xaml

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


        <!--#region 色など-->

        <!-- Active状態の背景色 -->
        <SolidColorBrush x:Key="ActiveBackColor" Color="Yellow"></SolidColorBrush>
       
        <!-- テーマカラー -->
        <LinearGradientBrush x:Key="BlueGradientBrush" EndPoint="0,1" StartPoint="0,0">
            <GradientStop Color="#FFFFFFFF" Offset="0"/>
            <GradientStop Color="#FFC4DDFF" Offset="0.987"/>
            <GradientStop Color="#FFDEECFF" Offset="0.535"/>
        </LinearGradientBrush>
        <!--#endregion-->


        <!--#region 継承元のスタイル-->
        <!-- 継承元のフォームベースのスタイル -->
        <Style x:Key="WindowStyleBase" TargetType="Window">
            <Setter Property="FontFamily" Value="MS Gothic" />
            <Setter Property="FontSize" Value="15"/>
            <Setter Property="Background" Value="{StaticResource BlueGradientBrush}"></Setter>
            <Setter Property="WindowStyle" Value="SingleBorderWindow"></Setter>
        </Style>

        <Style x:Key="CsTextBoxStyleBase" TargetType="controls:CsTextBox">
            <Setter Property="FontFamily" Value="MS Gothic" />
            <Setter Property="FontSize" Value="15"/>
            <Setter Property="TextAlignment" Value="Left"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Height" Value="25"/>


            <Style.Triggers>
               
                <!-- フォーカス時の背景色 -->
                <Trigger Property="IsFocused" Value="True">
                    <Setter Property="Background" Value="{StaticResource ActiveBackColor}"></Setter>
                </Trigger>
            </Style.Triggers>

        </Style>

        <Style x:Key="TextBlockStyleBase" TargetType="TextBlock">
            <Setter Property="FontFamily" Value="MS Gothic" />
            <Setter Property="FontSize" Value="15"/>
            <Setter Property="TextAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Height" Value="25"/>
        </Style>

        <Style x:Key="LabelStyleBase" TargetType="Label">
            <Setter Property="FontFamily" Value="MS Gothic" />
            <Setter Property="FontSize" Value="15"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="BorderBrush" Value="Black"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Height" Value="25"/>
        </Style>

        <Style x:Key="ButtonStyleBase" TargetType="Button">
            <Setter Property="FontFamily" Value="MS Gothic" />
            <Setter Property="FontSize" Value="15"/>
            <Setter Property="BorderBrush" Value="Black"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Height" Value="25"/>
            <Setter Property="Background" >
                <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="Gainsboro" Offset="0"/>
                    <GradientStop Color="#FF919191" Offset="1"/>
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>

           
            <Style.Triggers>

                <!-- フォーカス時の背景色 -->
                <Trigger Property="IsFocused" Value="True">
                    <Setter Property="Background" Value="{StaticResource ActiveBackColor}"></Setter>
                </Trigger>
            </Style.Triggers>

        </Style>

        <!--#endregion -->

        <!--#region 継承後のスタイル-->

        <Style x:Key="NormalLabel" TargetType="Label" BasedOn="{StaticResource LabelStyleBase}">
        </Style>
        <Style x:Key="RequiredLabel" TargetType="Label" BasedOn="{StaticResource LabelStyleBase}">
            <!--<Setter Property="Background" Value="#FFFF997B" />-->
            <Setter Property="Background" Value="#FF7BB7FF" />
        </Style>

        <Style x:Key="NormalTextBox" TargetType="controls:CsTextBox" BasedOn="{StaticResource CsTextBoxStyleBase}">
        </Style>
       
        <Style x:Key="NormalTextBlock" TargetType="TextBox" BasedOn="{StaticResource TextBlockStyleBase}">
        </Style>

        <Style x:Key="NormalButton" TargetType="Button" BasedOn="{StaticResource ButtonStyleBase}">
        </Style>

        <Style x:Key="LoginWindowStyle" TargetType="Window" BasedOn="{StaticResource WindowStyleBase}">
            <!--<Setter Property="Background">
                    <Setter.Value>
                        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                            <LinearGradientBrush.GradientStops>
                                <GradientStop Offset="0.0" Color="#FFB0FFB0"/>
                                --><!--<GradientStop Offset="1.0" Color="#FFD6D6D6"/>--><!--
                                <GradientStop Offset="1.0" Color="#FFB6DAA2"/>
                            </LinearGradientBrush.GradientStops>
                        </LinearGradientBrush>
                    </Setter.Value>
            </Setter>-->
        </Style>

        <Style x:Key="MenuWindowStyle" TargetType="Window" BasedOn="{StaticResource WindowStyleBase}">
        </Style>

        <!--#endregion-->

    </Application.Resources>
</Application>

Styleを継承するにはBaseOnを指定することでできる。
そのため、各コントロールのBaseStyleを定義し、それらを継承したスタイルを個別に作成するようにしている。
また、コントロールにフォーカスがあるときにはコントロールの背景色を変更したいので、Triggerを定義している。

どこかのタイミングでプロジェクトを上げたいと思う。

ガントチャート作成日記

久しぶり更新。
前回の稼働率の計算式でやった結果うまくいったのでバージョンを更新。


<DownLoad>
https://sites.google.com/site/mmbloguserfile/wai-bufairu/ProjectManager_Beta_0.0.0.3.zip

いろいろ忙しくて、少し間が空いてしまったので少しづつ思い出しながら直していこうと思う。

ガントチャート作成日記

土日を挟んだ進捗率の計算のメモ。


下記のようなスケジュールの場合で、進捗率が7割の場合はこんな感じで進捗が求まる気がする。

 水|木|金|土|日|月|火
 □|■|■|■|■|■|□

  ・土日は稼働日から外す。
  ・稼働日3日
  ・休日2日
  ・進捗率7割
  ・タスクのWidth:100

  ① 稼働予定の日数:3日
    進捗率の7割をかけると、2.1日分の作業が進んでいる
    そのため、月曜日を過ぎたところを進捗線したい。

  ② タスクの開始日に+1日ずつしていき、休日判定を行う。
    平日の場合は、稼働日変数に+1
    休日の場合は、休日日数を+1

    稼働日日数が2.1日を超えたらストップ。
    その時の休日日数を2.1日に足しこむ
    進捗線
       (2.1日+休日日数)÷5日(稼働日+休日)×タスクのWidth

これで線が引けるのではないだろうか!
後は実際に試してみてうまくいくかな・・・・。

LINQ to Objectについて

今日はLINQ to Objectをクエリ形式で利用してGroupBy句を作成。
GroupByで集計単位を指定しinto句でGroupオブジェクトを生成。

SQLと違ってまとまったGroupオブジェクトをループで取得できるので帳票を作りるときや総合計を出しつつ、明細を表示する場合などに便利。

WPFのDataGridでGroupingしたりTreeListViewなどで表示するといい感じに見える。

販売管理システムとかだと請求書などで鏡の部分を作成しつつ、明細の部分も表示するようなときに使いやすい。

    Private Sub btnGroup_Click(sender As Object, e As EventArgs) Handles btnGroup.Click
        Dim dtUri As New DataTable
        Dim drUriSample As DataRow

        ' ▼ 売上データ
        dtUri.Columns.Add(New DataColumn("売上No", GetType(Integer)))
        dtUri.Columns.Add(New DataColumn("売上日", GetType(String)))
        dtUri.Columns.Add(New DataColumn("得意先CD", GetType(String)))
        dtUri.Columns.Add(New DataColumn("商品名", GetType(String)))
        dtUri.Columns.Add(New DataColumn("売上数量", GetType(Decimal)))
        dtUri.Columns.Add(New DataColumn("売上金額", GetType(Decimal)))

        '   テストデータ
        drUriSample = dtUri.NewRow
        drUriSample("売上No") = 1
        drUriSample("売上日") = "20171031"
        drUriSample("得意先CD") = "00001"
        drUriSample("商品名") = "パソコン"
        drUriSample("売上数量") = 1
        drUriSample("売上金額") = 150000
        dtUri.Rows.Add(drUriSample)

        drUriSample = dtUri.NewRow
        drUriSample("売上No") = 2
        drUriSample("売上日") = "20171031"
        drUriSample("得意先CD") = "00002"
        drUriSample("商品名") = "マウス"
        drUriSample("売上数量") = 1
        drUriSample("売上金額") = 3000
        dtUri.Rows.Add(drUriSample)

        drUriSample = dtUri.NewRow
        drUriSample("売上No") = 1
        drUriSample("売上日") = "20171031"
        drUriSample("得意先CD") = "00001"
        drUriSample("商品名") = "モニター"
        drUriSample("売上数量") = 2
        drUriSample("売上金額") = 30000
        dtUri.Rows.Add(drUriSample)

        Dim lnqGroup = From drUri In dtUri.AsEnumerable
                       Group By TokCD = drUri("得意先CD")
                           Into grp = Group
                       Select New With {
                             .得意先CD = TokCD,
                             .grp = grp
                             }



        Dim strMsg As String = ""

        For Each lnqdr In lnqGroup
            strMsg += "得意先CD:" & lnqdr.得意先CD & vbCrLf
            strMsg += "合計金額:" & lnqdr.grp.Sum(Function(drChild) drChild("売上金額")) & vbCrLf

            'まとまった単位
            For Each drGrpChild In lnqdr.grp
                strMsg += "商品名:" & drGrpChild("商品名") & " 数量:" & drGrpChild("売上数量") & " 金額:" & drGrpChild("売上金額") & vbCrLf
            Next

            MsgBox(strMsg)
            strMsg = ""

        Next

    End Sub


ガントチャート作成日記

ガントチャートがいい感じに出来上がってきた。
マウスカーソルのある個所に線を引いたり、タスクの分割処理を実装したりしてみた。

あとは速度が気になるのと、進捗率が土日を挟むと上手く計算できないのが目立つんで次はそのあたりを改善しようと思う。

プロジェクトごとに土日は進捗率の計算から除くとか任意の日付を除けるようにするとかできるようにしようと思う。

とりあえずベータ版を公開してみる。

https://sites.google.com/site/mmbloguserfile/wai-bufairu/ProjectManager_Beta_0.0.0.2.zip

ガントチャート作成日記

今日はガントチャートとツリーリストビューでそれぞれ選択行がわかるように枠線をつけたかったので、いろいろ頑張ってようやく実装できた。


ガントチャート側はGridコントロールで実装している。それぞれの行に専用のDataTemplateを切り替えるようにして交互色を設定している。土日の列の色を変えるためにもDataTemplateを設定している。Panel.Zindexを変更することで、行の上に列のテンプレートが表示されるようにしている。

選択行の枠線を変えるために背景色が透明の行を作成し、Panel.Zindexをほかの内容より大きくすることで一番上に行が表示されるようにして後はイベントを頑張って設定した。

Xaml側

        <!-- 行の背景を描画するDataTemplate -->
        <DataTemplate x:Key="RowLineTypeDataTemplate" >
            <Rectangle  Stroke="Black" StrokeThickness="0.25" Fill="Transparent"  />
            <!--StrokeDashArray="10 10" を設定すれば点線になる-->
        </DataTemplate>

        <!-- 交互行の背景を描画するDataTemplate -->
        <DataTemplate x:Key="AlternateRowLineTypeDataTemplate" >
            <!--<Rectangle Fill="#FFEFF9EC" />-->
            <Rectangle Fill="#FFF0F0F0" StrokeThickness="0.25" />
        </DataTemplate>

        <!-- 選択行の見た目を変更するためにZindexを列のZIndexより大きいものを作成 -->
        <DataTemplate x:Key="StyleRowLineDataTemplate" >
            <Rectangle Fill="Transparent" MouseEnter="rectBack_MouseEnter" MouseLeave="rectBack_MouseLeave"/>
        </DataTemplate>

Cs側

            // 行の背景色を辺くするためのタスクをインスタンス化して追加
            for (int i = 0; i < _alltask.Count; i++)
            {
                // タスクの位置を設定
                _alltask[i].Cell.RowIndex = i;

                if (i % 2 == 0)
                {
                    // 列罫線を描画するためのタスクを新たにインスタンス化して追加
                    _GanttGridCells.Add(new GanttCell(GanttCell.TemplateType.RowLine) { ZIndex = 0, ColumnIndex = 0, ColumnSpan = ColumnCount, RowIndex = i, RowSpan = 1 });
                }
                else {
                    // 列罫線を描画するためのタスクを新たにインスタンス化して追加
                    _GanttGridCells.Add(new GanttCell(GanttCell.TemplateType.AlternateRowLine) { ZIndex = 0, ColumnIndex = 0, ColumnSpan = ColumnCount, RowIndex = i, RowSpan = 1 });
                }

                // 選択行の見た目を変更するためにZindexを列のZIndexより大きいものを作成
                _GanttGridCells.Add(new GanttCell(GanttCell.TemplateType.StyleRowLine) { ZIndex = 30, ColumnIndex = 0, ColumnSpan = ColumnCount, RowIndex = i, RowSpan = 1 });

            }

            // 土日の列の背景色を変更するためのタスクを生成
            for (int i = 0; i < ColumnCount; i++)
            {
                // 土日それぞれに合わせてTemplateTypeプロパティに値を設定するとTemplateSelecterが各値によってDataTemplateのKye名を生成し、描画に利用するDataTemplateを選択する
                if (ProjectStartDay.AddDays(i).ToString("ddd") == "土")
                {
                    _GanttGridCells.Add(new GanttCell(GanttCell.TemplateType.ColumnLineBlue) { ZIndex = 10, ColumnIndex = i, ColumnSpan = 1, RowIndex = 0, RowSpan = this.RowCount });
                }
                else if (ProjectStartDay.AddDays(i).ToString("ddd") == "日")
                {
                    _GanttGridCells.Add(new GanttCell(GanttCell.TemplateType.ColumnLineOrange) { ZIndex = 10, ColumnIndex = i, ColumnSpan = 1, RowIndex = 0, RowSpan = this.RowCount });
                }
                else if (Common.Holiday(ProjectStartDay.AddDays(i)).holiday != Common.HolidayInfo.HOLIDAY.WEEKDAY)
                {
                    // 平日以外(祝日・振替休日)の場合は日と同じ色を設定
                    _GanttGridCells.Add(new GanttCell(GanttCell.TemplateType.ColumnLineOrange) { ZIndex = 10, ColumnIndex = i, ColumnSpan = 1, RowIndex = 0, RowSpan = this.RowCount });
                }
                else
                {
                    _GanttGridCells.Add(new GanttCell(GanttCell.TemplateType.ColumnLine) { ZIndex = 10, ColumnIndex = i, ColumnSpan = 1, RowIndex = 0, RowSpan = this.RowCount });
                }
            }

いずれはこの辺りをまとめて資料にしようと思うが、試行錯誤しながらだからあとで作成日記を見ながらまとめようかなーと思う。

あとは逆にTreeListView側で選択した行の枠線をガントチャート側に反映させようと思う。

LINQ to Objectについて

LINQ to Objectはとても便利だけど、DataTableとかDataSetを使ったサンプルがあんまりないので、メモ。
DataTable自体があまり使われないのかもしれないけど、データベースからとってきた値をとりあえず入れて使う分には使いやすいのでよく使う。
プログラマーによってクラスの作り方がバラバラだったり、共通関数に渡すときに統一できたりとか、Webサービスからデータを渡すときに簡単に渡せたりするんで結構重宝している。

CSVとデータベースの値を結合したり、CSVとCSVを結合したりするときにもSQLみたいに書きたい要望があったりするのでそのサンプル。
※まれにデータが数百万行あったりして、SQLよりもLINQで処理したほうが早いケースがあったりするときに書き直したりする。

VB.NET ====

    ''' <summary>
    ''' Left Join
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    Private Sub btnLeftJoin_Click(sender As Object, e As EventArgs) Handles btnLeftJoin.Click
        Dim dtUri As New DataTable
        Dim drUriSample As DataRow

        ' ▼ 売上データ
        dtUri.Columns.Add(New DataColumn("売上No", GetType(Integer)))
        dtUri.Columns.Add(New DataColumn("売上日", GetType(String)))
        dtUri.Columns.Add(New DataColumn("得意先CD", GetType(String)))
        dtUri.Columns.Add(New DataColumn("商品名", GetType(String)))
        dtUri.Columns.Add(New DataColumn("売上数量", GetType(Decimal)))
        dtUri.Columns.Add(New DataColumn("売上金額", GetType(Decimal)))

        '   テストデータ
        drUriSample = dtUri.NewRow
        drUriSample("売上No") = 1
        drUriSample("売上日") = "20171031"
        drUriSample("得意先CD") = "00001"
        drUriSample("商品名") = "パソコン"
        drUriSample("売上数量") = 1
        drUriSample("売上金額") = 150000
        dtUri.Rows.Add(drUriSample)

        drUriSample = dtUri.NewRow
        drUriSample("売上No") = 2
        drUriSample("売上日") = "20171031"
        drUriSample("得意先CD") = "00002"
        drUriSample("商品名") = "マウス"
        drUriSample("売上数量") = 1
        drUriSample("売上金額") = 3000
        dtUri.Rows.Add(drUriSample)

        drUriSample = dtUri.NewRow
        drUriSample("売上No") = 1
        drUriSample("売上日") = "20171031"
        drUriSample("得意先CD") = "00001"
        drUriSample("商品名") = "モニター"
        drUriSample("売上数量") = 2
        drUriSample("売上金額") = 30000
        dtUri.Rows.Add(drUriSample)


        ' ▼ 得意先データ
        Dim dtTok As New DataTable
        Dim drTokSample As DataRow

        dtTok.Columns.Add(New DataColumn("得意先CD", GetType(String)))
        dtTok.Columns.Add(New DataColumn("得意先名", GetType(String)))
        dtTok.Columns.Add(New DataColumn("備考", GetType(String)))

        '   テストデータ
        drTokSample = dtTok.NewRow
        drTokSample("得意先CD") = "00001"
        drTokSample("得意先名") = "株式会社 システム作ろう"
        drTokSample("備考") = ""
        dtTok.Rows.Add(drTokSample)

        drTokSample = dtTok.NewRow
        drTokSample("得意先CD") = "00002"
        drTokSample("得意先名") = "株式会社 いろいろ販売"
        drTokSample("備考") = ""
        dtTok.Rows.Add(drTokSample)

        Dim dtUriIchiran As New DataTable
        Dim drIchiran As DataRow

        ' ▼ 売上一覧
        dtUriIchiran.Columns.Add(New DataColumn("売上No", GetType(Integer)))
        dtUriIchiran.Columns.Add(New DataColumn("売上日", GetType(String)))
        dtUriIchiran.Columns.Add(New DataColumn("得意先CD", GetType(String)))
        dtUriIchiran.Columns.Add(New DataColumn("得意先名", GetType(String)))
        dtUriIchiran.Columns.Add(New DataColumn("売上数量", GetType(Decimal)))
        dtUriIchiran.Columns.Add(New DataColumn("売上金額", GetType(Decimal)))

        Dim lnqIchiran = From drUri In dtUri.AsEnumerable
                         Group Join drTok In dtTok
                         On drUri("得意先CD") Equals drTok("得意先CD")
                         Into grp = Group
                         From drTokEmpty In grp.DefaultIfEmpty
                         Select New With {
                             .売上No = drUri("売上No"),
                             .売上日 = drUri("売上日"),
                             .得意先CD = drUri("得意先CD"),
                             .得意先名 = If(drTokEmpty("得意先名") Is Nothing, "", drTokEmpty("得意先名")),
                             .売上数量 = drUri("売上数量"),
                             .売上金額 = drUri("売上金額")
                             }

        For Each lnqdr In lnqIchiran
            drIchiran = dtUriIchiran.NewRow
            drIchiran("売上No") = lnqdr.売上No
            drIchiran("売上日") = lnqdr.売上日
            drIchiran("得意先CD") = lnqdr.得意先CD
            drIchiran("得意先名") = lnqdr.得意先名
            drIchiran("売上数量") = lnqdr.売上数量
            drIchiran("売上金額") = lnqdr.売上金額
            dtUriIchiran.Rows.Add(drIchiran)
        Next

    End Sub


C# ====


        private void btnLeftJoin_Click(object sender, EventArgs e)
        {
            DataTable dtUri = new System.Data.DataTable();
            DataRow drUriSample = null;

            // ▼ 売上データ

            dtUri.Columns.Add(new DataColumn("売上No", typeof(int)));
            dtUri.Columns.Add(new DataColumn("売上日", typeof(string)));
            dtUri.Columns.Add(new DataColumn("得意先CD", typeof(string)));
            dtUri.Columns.Add(new DataColumn("商品名", typeof(string)));
            dtUri.Columns.Add(new DataColumn("売上数量", typeof(decimal)));
            dtUri.Columns.Add(new DataColumn("売上金額", typeof(decimal)));

            //   テストデータ
            drUriSample = dtUri.NewRow();
            drUriSample["売上No"] = 1;
            drUriSample["売上日"] = "20171031";
            drUriSample["得意先CD"] = "00001";
            drUriSample["商品名"] = "パソコン";
            drUriSample["売上数量"] = 1;
            drUriSample["売上金額"] = 150000;
            dtUri.Rows.Add(drUriSample);

            drUriSample = dtUri.NewRow();
            drUriSample["売上No"] = 2;
            drUriSample["売上日"] = "20171031";
            drUriSample["得意先CD"] = "00002";
            drUriSample["商品名"] = "マウス"; ;
            drUriSample["売上数量"] = 1;
            drUriSample["売上金額"] = 3000;
            dtUri.Rows.Add(drUriSample);

            drUriSample = dtUri.NewRow();
            drUriSample["売上No"] = 1;
            drUriSample["売上日"] = "20171031";
            drUriSample["得意先CD"] = "00001";
            drUriSample["商品名"] = "モニター";
            drUriSample["売上数量"] = 2;
            drUriSample["売上金額"] = 30000;
            dtUri.Rows.Add(drUriSample);


            // ▼ 得意先データ
            DataTable dtTok = new DataTable();
            DataRow drTokSample = null;

            dtTok.Columns.Add(new DataColumn("得意先CD", typeof(string)));
            dtTok.Columns.Add(new DataColumn("得意先名", typeof(string)));
            dtTok.Columns.Add(new DataColumn("備考", typeof(string)));

            //   テストデータ
            drTokSample = dtTok.NewRow();
            drTokSample["得意先CD"] = "00001";
            drTokSample["得意先名"] = "株式会社 システム作ろう";
            drTokSample["備考"] = "";
            dtTok.Rows.Add(drTokSample);

            drTokSample = dtTok.NewRow();
            drTokSample["得意先CD"] = "00004";
            drTokSample["得意先名"] = "株式会社 いろいろ販売";
            drTokSample["備考"] = "";
            dtTok.Rows.Add(drTokSample);


            DataTable dtUriIchiran = new DataTable();
            DataRow drIchiran = null;

            // ▼ 売上一覧
            dtUriIchiran.Columns.Add(new DataColumn("売上No", typeof(int)));
            dtUriIchiran.Columns.Add(new DataColumn("売上日", typeof(string)));
            dtUriIchiran.Columns.Add(new DataColumn("得意先CD", typeof(string)));
            dtUriIchiran.Columns.Add(new DataColumn("得意先名", typeof(string)));
            dtUriIchiran.Columns.Add(new DataColumn("売上数量", typeof(decimal)));
            dtUriIchiran.Columns.Add(new DataColumn("売上金額", typeof(decimal)));

            var lnqIchiran = from drUri in dtUri.AsEnumerable()
                             join drTok in dtTok.AsEnumerable()
                             on drUri["得意先CD"] equals drTok["得意先CD"]
                             into tokGroup
                             from drTokEmpty in tokGroup.DefaultIfEmpty()
                             select new
                             {
                                 売上No = drUri["売上No"],
                                 売上日 = drUri["売上日"],
                                 得意先CD = drUri["得意先CD"],
                                 得意先名 = (drTokEmpty == null) ? "" : drTokEmpty["得意先名"],
                                 売上数量 = drUri["売上数量"],
                                 売上金額 = drUri["売上金額"]
                             };

            foreach (var lnqdr in lnqIchiran)
            {
                drIchiran = dtUriIchiran.NewRow();
                drIchiran["売上No"] = lnqdr.売上No;
                drIchiran["売上日"] = lnqdr.売上日;
                drIchiran["得意先CD"] = lnqdr.得意先CD;
                drIchiran["得意先名"] = lnqdr.得意先名;
                drIchiran["売上数量"] = lnqdr.売上数量;
                drIchiran["売上金額"] = lnqdr.売上金額;
                dtUriIchiran.Rows.Add(drIchiran);
            }

        }

===========

on句で結合するときに別の関数を間に挟んだりできるので結構便利

例)Private MaeZero(string Value,int Keta) As String   ->前ゼロ梅する関数


        Dim lnqIchiran = From drUri In dtUri.AsEnumerable
                         Group Join drTok In dtTok
                         On MaeZero(drUri("得意先CD"),5) Equals MaeZero(drTok("得意先CD"),5)
                         Into grp = Group
                         From drTokEmpty In grp.DefaultIfEmpty
                         Select New With {
                             .売上No = drUri("売上No"),
                             .売上日 = drUri("売上日"),
                             .得意先CD = drUri("得意先CD"),
                             .得意先名 = If(drTokEmpty("得意先名") Is Nothing, "", drTokEmpty("得意先名")),
                             .売上数量 = drUri("売上数量"),
                             .売上金額 = drUri("売上金額")
                             }

ただ、プロジェクト単位である程度書き方を統一しておかないとプログラマさんによっては書き方が全然違うので保守とか考えるときは注意したほうがいい。

みんなが知識ある人ばっかりだったらそんなに気にしなくてもいいかもしれないけど、開発の後半になって仕様変更とかで危なそうな個所を探すときにも書き方が一緒じゃないとGrepしても引っかからなかったりする。

パッと見でもわからないし・・・。

ガントチャート作成日記

今日はタスクの分割機能を実装してみた。

タスクを選んで右クリックでタスクを細分化することができる。
そのため、TODOリストの組み合わせという形ではなく、大きなタスクをざっと洗い出して、その中でさらに何をすればいいのかを細かく分けていけるようにしてみた。

タスクを選んで右クリック→タスクの分割を押下することで分割される。













タスクの分割設定ボタンを押下すると設定画面で分割数やタスク名を指定することができる。

不具合のある機能を修正し、タスクの分割機能などいくつかの機能を回収した。

公開URL:https://sites.google.com/site/mmbloguserfile/wai-bufairu/ProjectManager_Beta_0.0.0.1.zip

まだまだ作成中。

ガントチャート作成日記

今日もちょっとずつガントチャートを作成してみる。
なんかいろいろとバグがあるのでゆっくりつぶしながら、今日も機能を追加。

やっぱりテストとかよりも思いついた機能を実装しているほうが楽しい。

今日追加したのは、タスクの移動をしながら水平スクロールする機能。


右クリック+スクロールの場合は、水平スクロールするように機能を修正。
その時に合わせてタスクも横に移動するようにしてみた。

ほかにも、試しに使ってたらタスクを右クリックしたら「タスクの分割」を一気にできるようにする機能とかもほしいなーと思ったりしたので実装中。
  例)
    〇〇〇作業  ← 「タスクの分割」機能を実行
    ▲▲▲作業
     ↓
    〇〇〇作業
     ∟ 〇〇の準備
     ∟ 〇〇の資料作成
     ∟ 〇〇のデモプレゼン
    ▲▲▲作業

みたいな感じで、はじめは大きな枠でタスクを作って少しずつ細かいTODOに落とし込んでいくイメージ。

ちょっとずつバグもつぶれてきたので懲りずに近々ベータ版をアップしてみようかと思う。


ガントチャート作成日記

備考があるときだけ付箋っぽい画像を表示してカーソルが当たったら備考を表示するようにしたいなーと思ったので実装してみた。
慣れて着たらWPFはやりたいと思ったことをさくっと作れるので すごいと思う。




<ユーザーコントロール>
PopUpNote.xaml

<UserControl x:Class="ProjectManager.Controls.PopUpNote"
             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:ProjectManager.Controls"
             mc:Ignorable="d" >

    <!--フィルタ内容を選択するためのPopUpコントロール-->
    <Popup Name="popupInfomation" StaysOpen="False" Width="200" Height="180">
        <Border BorderBrush="Black" BorderThickness="0.5" Margin="0" Padding="0">
            <DockPanel  Margin="0">
                <TextBox BorderBrush="Black" Name="txtNotes" Background="LightYellow" x:FieldModifier="public" IsReadOnly="false" IsEnabled="True"></TextBox>
            </DockPanel>
        </Border>
    </Popup>

</UserControl>

    /// <summary>
    /// PopUpNote.xaml の相互作用ロジック
    /// </summary>
    public partial class PopUpNote : UserControl
    {
        public PopUpNote()
        {
            InitializeComponent();

        }
          
        private bool _isOpen = false;
        public bool IsOpen
        {
            get { return _isOpen; }
            set { _isOpen = value; popupInfomation.IsOpen = _isOpen; }
        }

        #region 添付プロパティ

        #region Notes

        /// <summary>
        /// ヘッダに表示する文字列
        /// DataGridFilterTextColumnHeaderクラスのHeaderTextプロパティに値を渡すための添付プロパティ
        /// </summary>
        //public string Notes
        //{
        //    get { return base.GetValue(NotesProperty) as string; }
        //    set { base.SetValue(NotesProperty, value); }
        //}
        public string Notes
        {
            get { return (string)this.GetValue(PopUpNote.NotesProperty); }
            set { this.SetValue(PopUpNote.NotesProperty, value); }
        }
        public static  DependencyProperty NotesProperty = DependencyProperty.RegisterAttached(
            "Notes", typeof(string),
            typeof(PopUpNote), new PropertyMetadata(string.Empty,
                                                                                new PropertyChangedCallback((sender, e) =>
                                                                                {
                                                                                    (sender as PopUpNote).OnNotesPropertyChanged(sender, e);
                                                                                })));
        private void OnNotesPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null) { return; }
            this.txtNotes.Text = e.NewValue.ToString();
        }

        #endregion

        #endregion


<呼び出し側>
                    <!-- ***情報*** -->
                    <GridViewColumn Header="Info" Width="30" HeaderContainerStyle="{StaticResource GridViewColumnHeaderStyle}">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <!-- Marginで左右から-6pxとしないとHearder部のタイトルとラインの位置がずれる.GridViewの既知の不具合 -->
                                <Border BorderBrush="Black"  BorderThickness="0,0,0.5,0" Margin="-6,0,-6,0">
                                    <DockPanel  >
                                        <Image x:Name="popTriger" MouseEnter="popup_MouseEnter" Source="Images/postit.png" Visibility="{Binding Memo,Converter={StaticResource visibilityConverter}}"></Image>
                                        <csControls:PopUpNote x:Name="popup" Notes="{Binding Memo}" IsEnabled="True" >
                                        </csControls:PopUpNote>
                                    </DockPanel>
                                </Border>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

実装例



すごいなーと思ったけど、ポップアップの中のテキストボックスが触れない。
貼り付けはできるっぽいけど・・・。簡単に調べた限りじゃわからんかった。
まぁいいってことにして少し使ってみてやっぱり変更したくなったら修正するようにしようと思う。

ガントチャート作成日記

少しづつ作っていたガントチャートがとりあえず、データを保存できるレベルになったので一端公開。

最新バージョンをダウンロードするように常に通知されるとか、タスクのカラーを変更できそうに見えるとか、Excel出力とか、帳票出力っぽいものとかボタンはあるけど動かないし、そもそも実装するかも未定だけど・・・。

とりあえず普通のタスク管理はできるのと、表示タブからいくつかの単位でまとめ直して表示することができる。

こまめにCtrl+Sで上書き保存してもらって変な動きしたらもう一回開きなおしてもらえばそれなりに使えるかも。

インストール不要なんで試してもらう分にはそれなりにいいかも。

DownLoad URL(ZipFile、Portable)
 => https://sites.google.com/site/mmbloguserfile/wai-bufairu/ProjectManager_Beta_0.0.0.0.zip

解凍してProjectManager.exeを起動してもらえば試せます。

<サンプル>

 表示タブ⇒作業者一覧 を選択


 作業者ごとにグルーピング(日付を変更すると元データも変更される)


 メイン画面=>タスク一覧を選択するとExcelっぽい感じで使える。
 Excelからのコピーペーストもできる
 ※空白セルがあるとずれる不具合がある。Excel上で全角空白か半角空白を入れてから張り付けてもらえばうまくいく。




ガントチャート作成日記

イナズマ線の表示の不具合を解消した。


イナズマ線はCellごとにPointCollection型のプロパティに値を指定している。
開始点、中間点、終了点はそれぞれ高さや行の幅×進捗率で計算して保持している。

        /// <summary>
        /// イナズマ線を表示
        /// </summary>
        /// <param name="targetTask">イナズマ線を表示するタスク</param>
        /// <param name="showPairThunderLine">ペアタスクのイナズマ線を表示するかどうか</param>
        public void ShowThunderLine(GanttGridTask targetTask,bool showPairThunderLine)
        {
            if (GanttGridCells == null) { return; }
            foreach (var rowItem in this.GanttGridCells.Where(i => i.DataTemplateType == GanttCell.TemplateType.ThunderLineRow && i.Task.Id.ToString() == targetTask.Id.ToString()))
            {
                // 通常タスクのイナズマ線を描画
                if (this.ThunderLineVisible)
                {
                    if (targetTask.Name != "" || targetTask.Name != null) { rowItem.ThunderLine = _GetThunderLinePoint(targetTask); }
                    else { rowItem.ThunderLine = null; }
                }

            }
        }

        /// <summary>
        /// イナズマ線のPointCollecctionを取得
        /// </summary>
        /// <param name="targetTask">イナズマ線を作成するタスク</param>
        /// <returns>
        ///  
        ///  ・基準日付時点でタスクが完了していないものは進捗位置までの線を引く
        ///    ⇒ 進捗率が1(完了)以外で、終了日
        ///  ・基準日までに開始できていないものは線を引く
        ///    ⇒ 進捗率が1(完了)以外で、終了日
        /// 
        ///  ・タスクの進捗率が基準日より遅れている場合に線を引く
        ///    ⇒ タスクの進捗率が1(完了)以外で、基準日より前にタスクの終了予定日がある
        ///      タスクの進捗率が1(完了)以外で、基準日より開始予定日が小さい場合
        ///  ・タスクの進捗率が基準日より進んでいる場合に線を引く
        ///
        /// </returns>
        private PointCollection _GetThunderLinePoint(GanttGridTask targetTask)
        {
            TimeSpan ts = this.ThunderLineBaseDate - ProjectStartDay; ;
            PointCollection _ThunderLine = new PointCollection();

            if ((targetTask.ProgressRate != 1 && targetTask.EndDay < this.ThunderLineBaseDate)
                || ((targetTask.ProgressRate != 1) && this.ThunderLineBaseDate > targetTask.StartDay)
                || ((targetTask.ProgressRate != 0) && this.ThunderLineBaseDate < targetTask.EndDay))
            {
                double startPoint = targetTask.Cell.ColumnIndex * this.ColumnWidth;
                double durectionPoint = targetTask.Cell.ColumnSpan * this.ColumnWidth;
                if (targetTask.ProgressRate == 0) { durectionPoint = durectionPoint * 0.01; }
                else { durectionPoint = durectionPoint * targetTask.ProgressRate; }

                _ThunderLine.Add(new Point(ts.Days * this.ColumnWidth, 0));
                _ThunderLine.Add(new Point(startPoint + durectionPoint, this.RowHeight / 2));
                _ThunderLine.Add(new Point(ts.Days * this.ColumnWidth, this.RowHeight));
            }
            else
            {
                _ThunderLine.Add(new Point(ts.Days * this.ColumnWidth, 0));
                _ThunderLine.Add(new Point(ts.Days * this.ColumnWidth, this.RowHeight));
            }

            return _ThunderLine;

        }

表現するためには、XAMLにDataTemplateを宣言しておいて、DataTemplateSelecterで変換している。

        <!-- イナズマ線を描画するためのDataTemplate -->
        <DataTemplate x:Key="ThunderLineRowLineTypeDataTemplate" >
            <Grid>
                <Polyline Points="{Binding ThunderLine}"  Stroke="Red" StrokeThickness="1" />
            </Grid>
        </DataTemplate>

ガントチャートを表現するためにItemControlに独自に作成したGanttTaskCell型をバインドしている。どのようなDataTemplateを利用するかはTemplateSelecterで切り替えている。そうすることで曜日によって色を変えたり、色々することができる。

詳細は全部完成してからいつか書きたいとは思う。

ついでに、垂直スクロールだけではなく、左クリックを押しながらホイールすれば水平スクロールもするように修正してみた。結構便利な気がする。


ガントチャート作成日記

昨日悩んでいた交互色について、プロパティを追加してバインドすることで解決した。
別dllのコントロールに対して、バインディングするのは気が引けるが今更かと思い直してバインドして解決した。
(TreeListViewとGanntGridはそれぞれ別々のdll)


やはり交互色があるほうがわかりやすい。
次はイナズマ線の不具合を直しつつ、ガントチャートの初期位置の表示を当日にするように修正してみる。

ガントチャート作成日記

TreeListViewにずっと交互色を付けたいと思っていた。調べたところAlternationCountプロパティを設定すればいいらしい・・・。

<dll呼び出し側>
<tree:TreeListView Name="treeView" AllowsColumnReorder="True" ItemsSource="{Binding GanttGridViewModel.Tasks}"
                               ScrollViewer.HorizontalScrollBarVisibility="Visible"  Background="White" ScrollViewer.VerticalScrollBarVisibility="Visible"
                               VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" AlternationCount="2">


<dllのGeneric.xamlを抜粋>
        <!-- コントロールテンプレートのトリガーを定義 -->
        <ControlTemplate.Triggers>
            <!-- 交互色を指定 -->
            <!-- AlternationCountで交互色のパターンを指定。色についてはTreeListViewのGeneric.xamlのItemsControl.AlternationIndexで指定 -->
            <!-- 奇数行 -->
            <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                <Setter TargetName="Border" Property="Background" Value="Transparent"/>
            </Trigger>
            <!-- 偶数行-->
            <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                <Setter TargetName="Border" Property="Background" Value="#FFF0F0F0" />
            </Trigger>
         
        </ControlTemplate.Triggers>

    </ControlTemplate>


できた!!!

と思いきや、広げると・・・・


(゜-゜)・・・・。
やはりNo列で判定して書いたほうが確実かなぁ・・・。
ValueConverterを実装するほうが簡単かもしれないので次はそっちでやってみる。

ゴールは遠い・・・。


ガントチャート作成日記

スケジュール管理用のガントチャートのツールを作成しているときに独自に作成したTreeListViewで選択されている行に「▶」を出力したいと思ったのでいろいろいじってみた。

<修正前>
 <修正後>

思ったより簡単にできた。さすがWPF!!

Xaml抜粋

<TextBlock Text=" ▶" HorizontalAlignment="Left" VerticalAlignment="Center"  Margin="0,0,3,0" Visibility="{Binding IsSelected,Converter={StaticResource visibilityConverter},  RelativeSource={RelativeSource Mode=FindAncestor,AncestorType =TreeViewItem}}"/>

VisibilityConverter .cs
    /// <summary>
    /// ResizeRectangle.xamlで利用している
    /// リサイズ用のThumbコントロールの表示非表示を制御している
    /// </summary>
    public class VisibilityConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool b = (bool)value;
            return b ? Visibility.Visible : Visibility.Collapsed;
        }

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

        #endregion
    }

やっていることはTextBlockに「▶」の文字を入れておく。表示非表示の制御をバインディングとValueConverterインターフェイスを実装。

バインディングは、親のTreeViewItemのIsSelectedプロパティを取得し、取得したプロパティの値(bool型)をValueConveterでVisibilityの値(列挙体のVisibility)に変換している。

試しに使っているといろいろ気になることがあるので修正中。果たして完成はいつになるのやら。

Gridコントロールへのバインドについて

GridコントロールのRow、Column、RowSpan、ColumnSpanに対してバインディングを行いたいと思って色々調べていると下記のサイトを見つけた。

https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/

GridコントロールのRow、Column、RowSpan、ColumnSpanに対してバインディングを行えるので、Canvasコントロールほど自由度は高くなく、方眼紙を塗りつぶすようなイメージでコントロールの配置を動的に変更できる。
ピクロス的な感じ。

Rowプロパティにバインドすることでバインドされている側のプロパティの値を変えればコントロールの位置を自分で変えることができる。
設定画面を作ればユーザーに自由にコントロールを配置してもらうことができ、単票形式の帳票であれば結構簡単に作れそうな気がする。

<ちょっと手を加えた>
    public class GridHelpers
    {
        #region RowCount Property

        /// <summary>
        /// Adds the specified number of Rows to RowDefinitions.
        /// Default Height is Auto
        /// </summary>
        public static readonly DependencyProperty RowCountProperty =
            DependencyProperty.RegisterAttached(
                "RowCount", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(-1, RowCountChanged));

        // Get
        public static int GetRowCount(DependencyObject obj)
        {
            return (int)obj.GetValue(RowCountProperty);
        }

        // Set
        public static void SetRowCount(DependencyObject obj, int value)
        {
            obj.SetValue(RowCountProperty, value);
        }

        // Change Event - Adds the Rows
        public static void RowCountChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || (int)e.NewValue < 0)
                return;

            Grid grid = (Grid)obj;
            grid.RowDefinitions.Clear();

            for (int i = 0; i < (int)e.NewValue; i++)
                grid.RowDefinitions.Add(
                    new RowDefinition() { Height = GridLength.Auto });

            SetStarRows(grid);

            // 行幅再設定
            SetRowHeight(grid);

        }

        #endregion

        #region ColumnCount Property

        /// <summary>
        /// Adds the specified number of Columns to ColumnDefinitions.
        /// Default Width is Auto
        /// </summary>
        public static readonly DependencyProperty ColumnCountProperty =
            DependencyProperty.RegisterAttached(
                "ColumnCount", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(-1, ColumnCountChanged));

        // Get
        public static int GetColumnCount(DependencyObject obj)
        {
            return (int)obj.GetValue(ColumnCountProperty);
        }

        // Set
        public static void SetColumnCount(DependencyObject obj, int value)
        {
            obj.SetValue(ColumnCountProperty, value);
        }

        // Change Event - Add the Columns
        public static void ColumnCountChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || (int)e.NewValue < 0)
                return;

            Grid grid = (Grid)obj;
            grid.ColumnDefinitions.Clear();

            for (int i = 0; i < (int)e.NewValue; i++)
                grid.ColumnDefinitions.Add(
                    new ColumnDefinition() { Width = GridLength.Auto });

            SetStarColumns(grid);

            // カラム幅再設定
            SetColumnWidth(grid);

        }

        #endregion

        #region StarRows Property

        /// <summary>
        /// Makes the specified Row's Height equal to Star.
        /// Can set on multiple Rows
        /// </summary>
        public static readonly DependencyProperty StarRowsProperty =
            DependencyProperty.RegisterAttached(
                "StarRows", typeof(string), typeof(GridHelpers),
                new PropertyMetadata(string.Empty, StarRowsChanged));

        // Get
        public static string GetStarRows(DependencyObject obj)
        {
            return (string)obj.GetValue(StarRowsProperty);
        }

        // Set
        public static void SetStarRows(DependencyObject obj, string value)
        {
            obj.SetValue(StarRowsProperty, value);
        }

        // Change Event - Makes specified Row's Height equal to Star
        public static void StarRowsChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;

            SetStarRows((Grid)obj);
        }

        #endregion

        #region StarColumns Property

        /// <summary>
        /// Makes the specified Column's Width equal to Star.
        /// Can set on multiple Columns
        /// </summary>
        public static readonly DependencyProperty StarColumnsProperty =
            DependencyProperty.RegisterAttached(
                "StarColumns", typeof(string), typeof(GridHelpers),
                new PropertyMetadata(string.Empty, StarColumnsChanged));

        // Get
        public static string GetStarColumns(DependencyObject obj)
        {
            return (string)obj.GetValue(StarColumnsProperty);
        }

        // Set
        public static void SetStarColumns(DependencyObject obj, string value)
        {
            obj.SetValue(StarColumnsProperty, value);
        }

        // Change Event - Makes specified Column's Width equal to Star
        public static void StarColumnsChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;

            SetStarColumns((Grid)obj);
        }

        #endregion

        #region ColumnWidth Property

        /// <summary>
        /// カラム幅設定用独自プロパティ
        /// </summary>
        public static readonly DependencyProperty ColumnWidthProperty =
            DependencyProperty.RegisterAttached(
                "ColumnWidth", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(0, ColumnWidthChanged));

        // 取得処理
        public static int GetColumnWidth(DependencyObject obj)
        {
            return (int)obj.GetValue(ColumnWidthProperty);
        }

        // 設定処理
        public static void SetColumnWidth(DependencyObject obj, string value)
        {
            obj.SetValue(ColumnWidthProperty, value);
        }

        // 値変更時処理
        public static void ColumnWidthChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;

            SetColumnWidth((Grid)obj);
        }

        #endregion

        #region RowHeight Property
        /// <summary>
        /// カラム幅設定用独自プロパティ
        /// </summary>
        public static readonly DependencyProperty RowHeightProperty =
            DependencyProperty.RegisterAttached(
                "RowHeight", typeof(int), typeof(GridHelpers),
                new PropertyMetadata(0, RowHeightChanged));

        // 取得処理
        public static int GetRowHeight(DependencyObject obj)
        {
            return (int)obj.GetValue(RowHeightProperty);
        }

        // 設定処理
        public static void SetRowHeight(DependencyObject obj, string value)
        {
            obj.SetValue(RowHeightProperty, value);
        }

        // 値変更時処理
        public static void RowHeightChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (!(obj is Grid) || string.IsNullOrEmpty(e.NewValue.ToString()))
                return;
            SetRowHeight((Grid)obj);
        }

        #endregion


        private static void SetStarColumns(Grid grid)
        {
            string[] starColumns =
                GetStarColumns(grid).Split(',');

            for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
            {
                if (starColumns.Contains(i.ToString()))
                    grid.ColumnDefinitions[i].Width =
                        new GridLength(1, GridUnitType.Star);
            }
        }

        private static void SetStarRows(Grid grid)
        {
            string[] starRows =
                GetStarRows(grid).Split(',');

            for (int i = 0; i < grid.RowDefinitions.Count; i++)
            {
                if (starRows.Contains(i.ToString()))
                    grid.RowDefinitions[i].Height =
                        new GridLength(1, GridUnitType.Star);
            }
        }

        // カラム幅設定の独自プロパティ設定用
        private static void SetColumnWidth(Grid grid)
        {
            for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
            {
                grid.ColumnDefinitions[i].Width = new GridLength(GetColumnWidth(grid));
            }
        }

        // 行幅設定の独自プロパティ設定用
        private static void SetRowHeight(Grid grid)
        {
            for (int i = 0; i < grid.RowDefinitions.Count; i++)
            {
                grid.RowDefinitions[i].Height = new GridLength(GetRowHeight(grid));
            }
        }


<使い方>
<ScrollViewer Name="scrollDetail" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Grid.Row="1" ScrollViewer.IsDeferredScrollingEnabled="True"
  ScrollViewer.PanningMode="VerticalOnly" >
<ItemsControl Name="ic" ItemsSource="{Binding Source={StaticResource GanttGridCellsSource}}" VirtualizingPanel.IsVirtualizing="True" 
  VirtualizingPanel.VirtualizationMode="Recycling" ItemTemplateSelector="{StaticResource ItemViewModelTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Name="gridCalender" helper:GridHelpers.RowCount="{Binding RowCount}" helper:GridHelpers.ColumnCount="{Binding ColumnCount}"
  helper:GridHelpers.ColumnWidth="{Binding ColumnWidth}" helper:GridHelpers.RowHeight="{Binding RowHeight}"
  PreviewMouseDown="gridCalender_PreviewMouseDown" Margin="0,0,0,25">
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding Path=ColumnIndex}"/>
<Setter Property="Grid.Row" Value="{Binding Path=RowIndex}"/>
<Setter Property="Grid.ColumnSpan" Value="{Binding Path=ColumnSpan}"/>
<Setter Property="Grid.RowSpan" Value="{Binding Path=RowSpan}"/>
<Setter Property="Grid.ZIndex" Value="{Binding Path=ZIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</ScrollViewer>

TreeListViewについて

TreeViewは1つの列しか表示できないので、複数の列を表示できるようなコントロールを探していたところ、下記のサイトにサンプルを見つけた。
(URL:https://www.codeproject.com/Articles/24973/TreeListView)

このサンプルをもとにdllを作成して、下記のような表現ができるようになった。


ガントチャートを表現するための独自コントロールと合わせることで、スケジュール管理用のソフトを作成できそう・・・!!

というわけで下記のようなものを作ってみた。

それっぽい感じにはなってきたが、使いにくいのでもうちょっと手を入れたい・・・。
つーか、タスク数が多くなると動きが遅くなる・・・。
500タスク程度は軽く扱えるように作りたい。できれば1000タスク。
作業者ごとの画面とかも表示したりしたいし・・・。

うーん、夢が広がってきて果たしていついったん完成とすることができるかなぁ。



PowerShellでEdgeを自動化(インストール不要。参考:郵便追跡サービス自動操作)

1.経緯について  RPAのソフトをインストールできないので、これまでVBSでCreateObjectでブラウザの自動操作をすることがたまにあった。 ※いざというときの手札として持っているだけで安心感が段違い  見た目上IEがインストールされていなくても、CreateObject...