プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

WPFのItemsControl.ItemTemplateでリストの一つ前を参照する

f:id:puni-o:20161115164946j:plain

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>

前の値(リストの一つ上の項目)から今回の値に変わったよ、的な表示をしています

実行結果は

f:id:puni-o:20161115165310p:plain

左側は初めに書いたもにで、右が更新後です

こんなことも簡単にできるんですねー

WPFつよい

WPFでShapeをResourceにして使いまわす

WPFに限らず今時のアプリではアプリ内で使うアイコン等をベクトルデータで持っていることが多いと思います

IllustratorSVGXAML

って感じでアイコンデータがやってきたりすると思いますが、これをそのまま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>

結果

f:id:puni-o:20161114160551p:plain

ポイント

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を書かないとどこか一つにしか出てこなくなります

x:Shared="False"とすればそれぞれにインスタンスを作ってくれます

x:Shared Attribute

これでかつる!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はマウスのドラッグアンドドロップで動くようにします

f:id:puni-o:20160921135656g:plain

こんな感じ

この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;
}

実行結果はこんな感じ

f:id:puni-o:20160921140513g:plain

ちなみにMSDNにも書いてある通りSilverlightでもこのイベントは拾えますよ