WPFのItemsControl.ItemTemplateでリストの一つ前を参照する
WPFのItemsControlはすごく強力だとおもいますよね
ItemsControl.ItemsSourceにリストをバインドして、ItemTemplateで中身を定義すると自由なリストが簡単に作れるので一度使うと癖になります
簡単な例だとこんな感じ
<ItemsControl ItemsSource="{Binding Numbers}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="5"> <TextBlock Text="{Binding}"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
Numbersはランダムな10個の数値リストです
実行結果はランダムな数値が縦に10個並ぶだけなので割愛
普段はこんな感じでお手軽に使えますがごくごくごく偶にアイテムの前の値が欲しい時があります(主にデータ設計に問題がある時ですかねぇ)
そんな時は
{Binding RelativeSource={RelativeSource PreviousData}}
で見ることができるんですね、今日知りました
先ほどのサンプルを更新すると
<ItemsControl Grid.Column="1" ItemsSource="{Binding Numbers}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="5"> <TextBlock Text="{Binding RelativeSource={RelativeSource PreviousData}}"/> <TextBlock Text=" -> "/> <TextBlock Text="{Binding}"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
前の値(リストの一つ上の項目)から今回の値に変わったよ、的な表示をしています
実行結果は
左側は初めに書いたもにで、右が更新後です
こんなことも簡単にできるんですねー
WPFつよい
WPFでShapeをResourceにして使いまわす
WPFに限らず今時のアプリではアプリ内で使うアイコン等をベクトルデータで持っていることが多いと思います
Illustrator → SVG → XAML
って感じでアイコンデータがやってきたりすると思いますが、これをそのままContentに張り付けるのは悪手ですよね(面倒な時を除く)
ということでアイコンデータはResourceDictionaryに入れて、使うところはKeyを指定するようにしましょう
Resource(App.xaml)
<Application.Resources> <Canvas x:Key="Icon1" x:Shared="False" Width="100" Height="100"> <Ellipse Width="100" Height="100" Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}}}"/> </Canvas> <Canvas x:Key="Icon2" x:Shared="False" Width="100" Height="100"> <Rectangle Width="100" Height="100" Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}}}"/> </Canvas> </Application.Resources>
MainWindow.xaml
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Button Grid.Row="0" Grid.Column="0" Content="{StaticResource Icon1}"/> <Button Grid.Row="0" Grid.Column="1" Content="{StaticResource Icon1}" Foreground="Blue"/> <Button Grid.Row="1" Grid.Column="0" Content="{StaticResource Icon2}"/> <Button Grid.Row="1" Grid.Column="1" Content="{StaticResource Icon2}" Foreground="Blue"/> </Grid>
結果
ポイント
Shape.Fill
Shape.Fillの指定ですが、ContentControlのForegroundを参照させたい時(Button.Foregroundとか)は上のような感じにしないと参照してくれません
TextBlockとかは何も指定しなければ親のForegroundを見てくれるのでそのつもりでいると???ってなります
上の場合ButtonBaseを継承しているコントロール内でしか効果ありませんが、まぁそれ以外の状況で色を適宜変えたいとかないよね、ね
ということでMainWindow.xamlのGrid.Column="1"にあるボタンではForegroundの指定色にShapeが変更されていることがわかります
x:Shared="False"
ResourceDictionaryに定義しているリソースはデフォルトで使いまわすようになっています(Brushとかそういうのは使いまわした方が効率いいですからね)
ですがCanvasは使いまわしちゃいけないですよね
x:Sharedを書かないとどこか一つにしか出てこなくなります
Buttonの中のContentControlのContentが何かのタイミングで消える
— ぷにお (@vl_o_lv) November 14, 2016
x:Shared="False"とすればそれぞれにインスタンスを作ってくれます
これでかつる!WPFつおい
2016/11/18 追記
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}}}"
より
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}"
の方がよさそうですね (ButtonBaseをContentControlに変更)
WPFのCanvas上で動くShapeを線でつなぐ
とりあえずこんな感じの奴を用意します
<Canvas> <Rectangle x:Name="Rect1" Width="50" Height="50" Fill="Cyan" Canvas.Left="0" Canvas.Top="0"/> <Ellipse x:Name="Ellipse1" Width="50" Height="50" Fill="Magenta" Canvas.Left="200" Canvas.Top="200"/> <Line x:Name="Line1" StrokeThickness="5" Stroke="Blue" X1="0" X2="200" Y1="100" Y2="100"/> </Canvas>
Rect1はStoryboardでびよびよ動かして、Ellipse1はマウスのドラッグアンドドロップで動くようにします
こんな感じ
このRect1とEllipse1の中心をLine1で結ぶのが今回のお題
使うのはこれです
CompositionTarget.Rendering イベント (System.Windows.Media)
MSDNによるとレンダリングの直前に呼ばれるイベントだそうです。 ということでこの時のそれぞれの座標を取得してLine1のプロパティ変えれば逝けますね
public MainWindow() { InitializeComponent(); CompositionTarget.Rendering += CompositionTarget_Rendering; } private void CompositionTarget_Rendering(object sender, EventArgs e) { Line1.X1 = Canvas.GetLeft(Rect1) + Rect1.ActualWidth / 2; Line1.Y1 = Canvas.GetTop(Rect1) + Rect1.ActualHeight / 2; Line1.X2 = Canvas.GetLeft(Ellipse1) + Ellipse1.ActualWidth / 2; Line1.Y2 = Canvas.GetTop(Ellipse1) + Ellipse1.ActualHeight / 2; }
実行結果はこんな感じ
ちなみにMSDNにも書いてある通りSilverlightでもこのイベントは拾えますよ