C# / WPF

C#

記事一覧

memo

DynamicObject

基礎知識

命名規則

良い記事なので目を通した方がよい。

Names of Type Members - Framework Design Guidelines
Learn the guidelines for naming type members in .NET, such as methods, properties, events, and fields.

整数同士の割り算

int / intをやると小数がでる場合であっても、返り値がintのため小数点以下は無視される。

回避するには計算式の値のいずれかをdoubleやfloatに変換してやれば、その精度で計算される。
(double) x / y

もしくは2で割るような場合だと、
5 / 2 とせずに5 / 2.0としてintでないことを明示することで小数点も計算される。

ミリ秒とTimeSpan型の変換

int型のmsecをTimeSpan.FromMilliseconds()に渡せばTimeSpan型に変換してくれます。

TimeSpan timeSpan = TimeSpan.FromMilliseconds( int msec );

逆にTimeSpan型をミリ秒に直すにはTotalMillisecondsを使います。

double msec = timeSpan.TotalMilliseconds;
int msec = (int)timeSpan.TotalMilliseconds;

TimeSpan型はミリ秒以下も扱えるのでTotalMillisecondsで値を取り出すとdoubleで返ります。
ミリ秒単位で欲しければint型にキャストしましょう。

小数点以下を取得

1で割った余りを取得する。

input % 1.0

Nullable DateTimeからYearやMonthを取得

DateTime? Date { get; set; }
int Month = Date.Month

上記のようなことをやると、
‘System.Nullable’ does not contain a definition for ‘Year’
などと怒られます。

ヌル許容型だと値を持たないことがあるので、以下のように.Valueを挟む必要があります。

Date.Value.Year
Date.Value.Month

また、値を持つかどうかを判定するためにDate.HasValueというプロパティを用いてboolを確認できます。

Enumerableで使えるメソッド

  • First
  • FirstOrDefault
  • Where
  • Any
Hoge.First(x x=> x.name == "fuga")

Firstに条件を付けた場合、条件に合致する要素がない時はSystem.InvalidOperationException
FirstOrDefaultを使った場合は、参照型のクラスの場合はNullが返る。
値型の場合は、デフォルト値(intだと0)が返る。
ちなみにSource側のリストに要素がないとき、ArgumentNullExceptionとなる。

複数の戻り値 out

C#は戻り値が1つなのが標準であり、複数欲しい場合には2通りのやり方がある。
1つは引数にoutを付与することで、変数の宣言を兼ねて値を取り出せる。
もう1つが戻り値にタプルを指定することで、タプル内に複数の値を入れて取り出せる。

public void GetNumber(out int num1, out int num2)
{
    num1 = 5;
    num2 = 15;
}

上記のメソッドを呼んだ側は、num1, num2をそのまま宣言された変数として扱える。

Tupleを使う場合は以下。こっちの方が見やすい気もする。

public (int num1, int num2) GetNumber()
{
    int num1 = 5;
    int num2 = 15;
    return (num1, num2)
}

var hoge = Fuga.GetNumber()
Console.WriteLine(hoge.num1)  => 5
Console.WriteLine(hoge.num2)  => 15

String[]をObservableCollectionに変換

愚直にforeachで回すのが汎用的です。

ObservableCollection<string> StringCollection = new ObservableCollection<string>()
string[] stringArray = new string[5] { "a", "b", "c", "d", "e"};

foreach(string x in stringArray)
{
    StringCollection.Add(x);
}

ArrayとCollectionの間で変換をしないのであれば以下の方が単純です。

this.StringCollection = new ObservableCollection<string>( stringArray );

Xaml

クリックさせない

 IsHitTestVisible="False"

DataGrid

DataGrid – 仮想化

VirtualizingStackPanel.VirtualizationMode="Standard" 

DataGrid – 内部に設置したボタンからCommandのBinding

<DataGridTemplateColumn Header="ボタン" Width="100">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Button Content="表示" Command="{Binding Path=DataContext.SearchReset, RelativeSource={RelativeSource AncestorType=DataGrid }}" CommandParameter="hogehoge"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

上記のようにカラムにボタンを設定した場合、CommandにBindingするには一手間かかる。
DataGridのDataContextはItemsSourceに渡された内容となっているため、単にCommandからBindingしても必要なCommandにアクセスできない。
方法としては、RelativeSourceをDataGridまで遡ることで、WindowのDataContextにアクセスできるようになり、必要なViewModelに設定したCommandへ到達できる。

DataGrid – Background Color

意識するべきBackgroundは3つ。

  • DataGrid
  • DataGrid.RowStyle
  • DataGrid.CellStyle
ボタンカラムはチェックが付いてない箇所はHiddenに設定しています。
なお選択行は選択されるとBackgroundのOpacityのみが0.5になるようにしており、透過度が上がっていますが実際はRowStyleの黒が透けて濃く表示されています。
<DataGrid Background="red">

DataGridの全領域だが、Fullサイズで表示している場合には通常見えない。
見えてしまう箇所として、スクロールバーが出た場合などのカラムヘッダー横、Rowヘッダー下スペースなどが相当する。

<DataGrid.RowStyle>
    <Style TargetType="{x:Type DataGridRow}">
        <Setter Property="Background" Value="Black"/>
    </Style>
</DataGrid.RowStyle>

セルの色かと思いきやセルの色とは排他にならず、別々に設定できる。
具体的にはセルの色と、DataGrid幅以下のカラム幅で展開している場合の余剰スペース。
セルの中身をHiddenにした時などもRowのBackgroundが表示される。
ザックリ言うと、カラムヘッダーが存在している領域の横幅いっぱいまで、つまり横1列の色を指定できます。

<DataGrid.CellStyle>
    <Style TargetType="{x:Type DataGridCell}">
        <Setter Property="Background" Value="Gray"/>
    </Style>
</DataGrid.CellStyle>

RowStyleと排他になりそうだが、実は両方併存する。
見た目上はCellStyle>RowStyleの強弱関係がある。
Rowとは違い、セルが存在しない箇所には色はつかない。(カラムが存在せず余っているスペース等)
セルがHiddenやCollapseになっている箇所に色はつかない。

なお特定のカラムにCellStyleを設定していた場合、汎用のCellStyleより優先される。
設定されていないプロパティが上書きでたりするわけでもなく、カラムに設定したCellStyleのみが発現する。

DataGrid – 選択行を暗く表示する

実は選択行を暗くするという要望を実現するときに、上記の強弱関係が利用できる。
z-indexがCell>Row>DataGridの順の色になっているため、
CellStyleにOpacityを設定した場合、RowStyleの色が透過する。

通常、RowのBackgroundはWhiteになっているため、Opacityを設定すると明るくなる。
逆にRowのBackgroundをBlackにしておけば、Opacityを設定すると暗くできる。

DataGrid – 選択行の透過

<DataGrid>
    <DataGrid.CellStyle>
        <Style TargetType="{x:Type DataGridCell}">
            <Setter Property="Background" Value="Gray"/>
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Opacity" Value="0.5" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </DataGrid.CellStyle>
</DataGrid>

透過と聞くと、安易にこんな感じに試してみたくなるかもしれない。
でもセルに対してOpacityを設定するとBackgroundのOpacityのみならず、Foreground等のOpacityにも効いてしまい、予想と違う結果になるだろう。

実際に変更したいのはBackgroundのOpacityだけだ。
Backgroundは設定すると、実際にはSolidColorBrushが当たっている。
こんな感じで内部的に読み替えてくれている。(opacity部分は除く)

<TextBlock Text="Hoge">
    <TextBlock.Background>
        <SolidColorBrush Color="Gray" Opacity="0.5" />
    </TextBlock.Background>
</TextBlock>

上記のようにColorBrushには直接Opacityも設定できます。
なのでSolidColorBrushのOpacityさえ変更できれば、Foregroundやその他の要素に影響を及ぼさない。

だったら下のように書いたらどうかというと、全くダメ。

<Setter Property="Background.Opacity" Value="0.5"/>

悩みに悩んでネットの海をさまよって見つけたのがこちら。
Storyboardを使えばいけるらしい。

<Trigger Property="IsSelected" Value="True">
    <Trigger.EnterActions>
        <BeginStoryboard>
            <Storyboard Storyboard.TargetProperty="Background.(SolidColorBrush.Opacity)">
                <DoubleAnimation To="0.5" Duration="0" />
            </Storyboard>
        </BeginStoryboard>
    </Trigger.EnterActions>
    <Trigger.ExitActions>
        <BeginStoryboard>
            <Storyboard Storyboard.TargetProperty="Background.(SolidColorBrush.Opacity)">
                <DoubleAnimation To="1" Duration="0" />
            </Storyboard>
        </BeginStoryboard>
    </Trigger.ExitActions>
</Trigger>

Storyboardって何?って聞かれても困るけど、とりあえずこれで動くので良しとしよう。

DataGrid – 選択行の色を変更

<DataGrid.CellStyle>
    <Style TargetType="{x:Type DataGridCell}">
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="Orange" />
            </Trigger>
        </Style.Triggers>
    </Style>
</DataGrid.CellStyle>

DataGrid.CellStyleはOKで、DataGrid.RowStyleで試したがダメ。
何故かっていうと、上記の例がIsSelectedの時をTriggerにしているせいで、デフォルトのCellStyleが上書きで効いてしまうからです。なので他の条件であればRowStyleでやってもセルの色は変わります。
逆に上記の例をRowStyleでやった場合、デフォルトのCellStyleがカラムのある部分には適応されるが余白部分にはCellStyleは適応されないので、余白部分にはRowStyleで設定した色がつく。
ちなみにIsSelected以外にもセル単体選択を許容している場合はIsFocusedも使え、その場合は選択したセルのみ背景色を変更できる。

DataGrid – 特定カラムの色を変更

<DataGrid>
  <DataGrid.CellStyle />
</DataGrid>

データグリッド全体に適応する場合は上のようにDataGrid直下にCellStyleを設定するので構わないが、特定のカラムのみ適応したい場合がある。

<DataGrid>
  <DataGrid.Columns>
    <DataGridTextColumn>
      <DataGrid.CellStyle />
    </DataGridTextColumn>
  <DataGrid.CellStyle />
</DataGrid>

その場合には上記のように適応したいカラムの中にネストする。
ちなみにヘッダーをカスタマイズしたい場合も同様で、
DataGrideTextColumn.HeaderStyleをネストすればそのカラムのみヘッダー様式を変更できる。

ヘッダー全般を変更をする場合は以下を用いる。

<DataGrid.ColumnHeaderStyle>

DataGrid – 特定のカラムの値に応じて表示を変更する

IsCheckedがTrueの時だけ表示したい、セルが特定の値だったら色を変えたい、とか要望がありますよね。そういう時はDataTriggerを使ってプロパティの値に応じてCellStyleやRowStyleを変更します。

<DataGrid.RowStyle>
    <Style TargetType="{x:Type DataGridRow}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=EditFlag}" Value="Remove">
                <Setter Property="Visibility" Value="Collapsed"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=EditFlag}" Value="Add">
                <Setter Property="Background" Value="#FFD700"  />
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=EditFlag}" Value="Update">
                <Setter Property="Background" Value="#FFD700"  />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>

Dataではないプロパティ(例えばIsSelectedなど)を使う場合は、DataTriggerではなく、単なるTriggerを使いましょう。
複数条件を使う場合にはMultiDataTriggerやMultiTriggerを使えます。
MultiTriggerの場合は、Conditionに設定した内容が全てTrueの時にSetterが有効になります。

<DataGrid.RowStyle>
    <Style TargetType="{x:Type DataGridRow}">
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding Path=Color}" Value="Red">
                    <Condition Binding="{Binding Path=IsChecked}" Value="True">
                <MultiDataTrigger.Conditions>
                <Setter Property="Background" Value="Red"  />
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>

DataGrid – Styleの強弱関係

DataGridに限った話ではないですが、やりがちなので書いておきます。
絶対的なルールは、「親で明示されている方が強い」。

以下の場合はRedになる。(Styleは無視される)

<DataGrid Background="Red">
    <DataGrid.Resources>
        <Style TargetType="{x:Type DataGrid}">
            <Setter Property="Background" Value="Blue"/>
        </Style>
    </DataGrid.Resources>
</DataGrid>

DataGrid – セルにボタンを表示

<DataGrid.Columns>
    <DataGridTextColumn  Header="日付" Binding="{Binding Path=Date, StringFormat=yyyy/MM/dd}"/>

    <DataGridTemplateColumn Header="ボタン">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <Button Content="表示" />
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
</DataGrid.Columns>

上側のDataGridTextColumnが通常のカラムの設定方法ですね。
TextやList、CheckBox等の一般的なものは最初から定義されています。
しかし、ボタンを表示したり少し変わったことをしようとすると自前で実装しなければなりません。そういう時に使うのがTemplateColumnになります。
DataTemplate以下に実際に表示したい内容を書き込んで使います。

TemplateColumnにスタイルを設定したい場合には、上記に続けて以下のように書きます。

<DataGridTemplateColumn.CellStyle>
  <Style TargetType="{x:Type DataGridCell}">
    <Style.Triggers>
      <Trigger Property="IsSelected" Value="True">
        <Setter Property="Visibility" Value="Visible" />
      </Trigger>
      <Trigger Property="IsSelected" Value="False">
        <Setter Property="Visibility" Value="Collapsed" />
      </Trigger>
    </Style.Triggers>
  </Style>
</DataGridTemplateColumn.CellStyle>

ちなみに重要なことであるが、StyleでSetter Property=”Template”とやると実装の中身をすべて自分で作ることを宣言するに等しい。つまりもともとあったコンテンツのTemplateは無視されて、以下に記述するテンプレートが実際に適用される。

<DataGridTemplateColumn.CellStyle>
  <Setter Property="Template">
    <Setter.Value>

DataGrid – Rowヘッダーにチェックボックスをつける

DataGridにチェックボックスを付けたいという要望は一般的だと思いますが、RowHeaderとIsSelectedを使って説明されているケースが私の知る限りではなかったので書き残しておきます。

よくあるのはModelにIsChecked的なboolプロパティを作って、それをカラムに表示するというやり方です。それはそれで目的は達成されるのですが、ModelにViewで使うだけのプロパティを設定するのもおかしな話だと思います。
ViewModel用にboolなしのModelを継承した、bool付きの新たなClassを作るやり方も考えたのですが、凝ったことをしないのであれば今回解説する方法がラクチンです。

<DataGrid.RowHeaderTemplate>
    <DataTemplate>
        <Grid>
            <CheckBox IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
        </Grid>
    </DataTemplate>
</DataGrid.RowHeaderTemplate>

//これ以下は蛇足です
<DataGrid.RowHeaderStyle>
    <Style TargetType="{x:Type DataGridRowHeader}">
    </Style>
</DataGrid.RowHeaderStyle>

至極単純ですが、RowHeaderにCheckBoxを設置しています。
その上でIsSelectedプロパティをBindしてあげることで、選択状態になれば即座にチェックボックスがOnになります。

このやり方の場合、Modelを触る必要がないし、IsSelectedという便利なプロパティが標準で存在するのでxaml以外は何もする必要がありません。xamlも上記をコピペするだけです。

さらにこの方法だと全選択チェックが非常に簡単です。
RowHeaderとColumnHeaderの交点、つまりDataGridの左上をクリックするだけで全選択になります。普通なら全選択用のチェックボックスやボタンを設置し、CommandをBindingして、List内の要素をforeachで1つずつIsChecked=Trueにするという長ったらしい処理を書かなければなりません。ですが、上記の方法ならxamlコピペだけで全選択までできてしまう。

このやり方じゃあ表示がそうなるだけで、選択されていることがデータとして取り出せないから使えねーよって思われる方もいるかもしれませんが、そんなことはありません。SelectedItemsはバインドこそできないものの、ちゃんとobjectを取り出せますよ。

DataGrid – CurrentCellの初期設定値をクリアしたい

SelectedIndex="{Binding Path=SelectedIndex}"

SelectedIndexをBindingして値を0に設定していると起動時に1つ目のRowが選択された状態になる。非選択状態にしたい場合は初期値を-1とすればOK。

IsSynchronizedWithCurrentItem=”True”
としていても1行目が選択された状態になるようだ。
IsSynchronizedWithCurrentItemが必要な場合でも、SelectedIndex=-1でBindingしていれば非選択の状態となる。

DataGrid – IsSelectedがItemSourceを更新した場合にも維持できるか?

同じCollectionをClear()、Add()してもIsSelectedは保持されない。
Clear()せずに単にAddするだけならIsSelectedは維持される。

Entityのプロパティが更新された時にCollectionの要素に変更を反映したい、かつIsSelectedも維持したい場合は、ItemsSourceに入れたCollection自体の要素のプロパティを変更してあげる必要がある。

DataGrid – セルに配置したボタンからCommandをBinding

RelativeSourceをDataGrid まで遡って、DataContext.**Commandとする。

<Button Content="Hoge" Command="{Binding Path=DataContext.AddDataCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}" CommandParameter="{Binding Path=SelectedItems,RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"  >

DataGrid – RowがCollapsedの時のドラッグによるRow選択

値によってVisibilityをCollapsedにする設定でも、CollapsedされたRowを跨ってドラッグすると、 CollapsedされたRowもIsSelected = trueになってしまう。
この状態のままSelectedItemsをドラッグで取得すると、意図せずにCollapsedのItemも拾ってしまうので困る。
対処法としてはアイテムを隠したい場合はCollapsedにせずに、ListやCollectionからRemoveするしかない。
見えないアイテムを選択できないようにできないものか?

TextBox

選択中のカーソルの色指定

CaretBrushで指定できます。

<TextBox CaretBrush="Black"/>

LinearGradientBrush等の複雑な色指定をするなら下のように書きます。

<TextBox>
    <TextBox.CaretBrush>
        <SolidColorBrush Color="AliceBlue"/>
    </TextBox.CaretBrush>
</TextBox>

ビルド時のエラー

同一のキーを含む項目が既に追加されています。

該当のcsprojファイルに同じファイルが2回記述されている場合にでる。
↓のような感じで離れて2か所に記述があった。

<Page Include="Views\Resources\Dictionary.xaml">
  <Generator>MSBuild:Compile</Generator>
  <SubType>Designer</SubType>
</Page>
・・・
<Page Include="Views\Resources\Dictionary.xaml">
  <Generator>MSBuild:Compile</Generator>
  <SubType>Designer</SubType>
</Page>

Conflictを解消する際に、別々の箇所に挿入されたことに気付かず誤って両方残すと出た。

コメント