Open17

WPFのメモ

rakrak

visual studioでプロジェクト作成すると最初から作られているファイル。
Application.StartupUriがアプリケーション起動時の読み込み画面

rakrak

以下みたいに画面にまとまったパーツを配置する場合、Gridとformを使いページの挿入で配置するのがいい?
そうすれば1つのxamlにすべてのフォームを書くことなく、ページというxaml単位で管理していける。
が一番いい方法かはわからない。

rakrak

以下みたいに画面にまとまったパーツを配置する場合、Gridとformを使いページの挿入で配置するのがいい?

UserControlで分割したほうがいい。ページは画面遷移をしたい意図で使うべきとのこと。frameとか差し込み方がURIクラスを使用しているので確かにその通り。
e.g) frame.Source = new Uri("/SearchPage.xaml", UriKind.Relative);

また、Gridじゃなくても、<StackPanel>と<WrapPanel>で領域を分けることができる。radio buttonはパネルでグールピングされるのでこっちのほうが都合もいい?

rakrak

Windowに配置したUserControlのイベントからその配置したWindowのコントロールを操作するにはどうすればいい?
操作できなくても、WindowからUserControlのイベントを検知するにはどうすればいい?

やりたいこと。
以下のようなWindowで

  • ButtonはUserControlから設定(水色部分がそう)
  • LabelはMainWindowで設定

ボタンを押したらMainWindowの★マークを変更したい。

rakrak

参考: https://social.msdn.microsoft.com/Forums/ja-JP/7d5cf8c2-f3f0-45fb-9dd7-87a9069c2ddb/usercontrol233761236312425window35242124081239812452125051253112488?forum=wpfja

<local:>ButtonBase.Click="ChangeCircle"を追加すればいい。
→delegateやEventHandlerとしてイベント追加はよくわかってない。

MainWindow.xaml

<Window x:Class="test.MainWindow"
     //~略~
    >
    <Grid>
        <StackPanel >
            <local:MyUserControl1 ButtonBase.Click="ChangeMark"/>
            <WrapPanel>
                <Label>ボタンを押すとここが変わります→</Label>
                <Label x:Name="ChangeLabel">★</Label>
            </WrapPanel>
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

// classにイベントの関数を定義する
private void ChangeMark(object sender, RoutedEventArgs e)
        {
            this.ChangeLabel.Content = "●";
        }
rakrak

ButtonBase.Click=""だとそのUserControlのボタンクリックイベントすべてを検知する。
ボタンが複数ある場合は次のようにする。

  1. UserControl.xamlで、ボタンにNameを設定する
<UserControl
    ~略~
    >
    <Button x:Name="changeMaru" Content="maru"/>
    <Button x:Name="changeSikaku" Content="sikaku"/>
</UserControl>
  1. MainWindow.xaml.csでe.OriginalSource as Buttonを取得しNameで識別する
private void ChangeMark(object sender, RoutedEventArgs e)
        {
            var button = e.OriginalSource as Button;
            if (button.Name == "changeMaru")
            {
                this.ChangeLabel.Content = "●";
            }
            else if(button.Name == "changeSikaku")
            {
                this.ChangeLabel.Content = "■";
            }
            
        }

すべてのボタンを検知してしまうので、検知したいボタンだけをリッスンする方法はどうやる?

rakrak

これはMVVMとUserControlをViewModelでプロパティとして持っておくやり方がベスト。

rakrak

<DataTemplate>に名前を付けてもソースコードからはアクセスできない。
事前に定義不可だが、xamlで追加すること自体はvs上でエラーは出ない。

<ListView x:Name="previewListView" Grid.Column="1" Grid.Row="1" SelectionMode="Extended" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel/>
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid x:Name="viewCrid" Width="300" Height="200">
                        <!-- ここにデータを追加する -->
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

DataTemplate > Gridに x:Name=.viewCridとしているが単純にその名前を使うだけではコードからアクセスできない。

以下の方法でアクセスできる。

ContentPresenter を見つけて、その ContentPresenter に設定されている DataTemplate 上で FindName を呼び出す必要があります。

https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/data/how-to-find-datatemplate-generated-elements?view=netframeworkdesktop-4.8

http://main.tinyjoker.net/Tech/CSharp/WPF/DataTemplate��ưŪ���������줿�����ȥ������˥�����������.html

ListeViewでのやりかたがわからない、、

rakrak

コントロールのイベント通知にはDelegateCommandを使うの良さそう。
プロパティの変更通知にはSetPropertyを使うと楽。
上記2つPrismフレームワークなので、nugetでインストールして使う

プロパティの通知はこんなに簡単になります。

SetPropertyなし
public string Data{
    get => this._Data;
    set {
        this._Data = value;
        RaisePropertyChanged("Data");
    }
 }
SetProperty(Prism)あり
public string Data{
    get => this._Data;
    set => SetProperty(ref this._Data, value);
 }
rakrak

Prismでプロパティが変わったときにコントロールのCanExecuteを実行する方法メモ

// プロパティ
public bool IsProcessing1 {
  get { return _isProcessing1; }
  set { SetProperty(ref _isProcessing, value); }
}

// プロパティ
public bool IsProcessing2 {
  get { return _isProcessing2; }
  set { SetProperty(ref _isProcessing2, value); }
}

public DelegateCommand<object> EditButton_Click_Command { get; private set; }

// コンストラクタ
public Test(){
  this.EditButton_Click_Command = new DelegateCommand<object>(EditButton_Click, CanEditButton_Click)
  .ObservesProperty(() => IsProcessing1)   // 監視対象のプロパティ設定
  .ObservesProperty(() => IsProcessing2);  // メソッドチェーンで複数指定可能
}


void EditButton_Click(object param){
   // 処理
}

// 実行可能判定に使用するメソッド
// ObservesPropertyで指定したプロパティが変わった場合にも実行される
bool CanEditButton_Click(object param) {
  return !(IsProcessing1|| IsProcessing2);
}

第2引数のメソッド(上の例ならCanEditButton_Clickメソッド)の返り値がtrueなら押下可能、falseなら押下不可。
また、DelegateCommandの第2引数を指定しない場合は常にtrueになる。

rakrak

VMの基底クラスでDelegateCommandの登録様式は定義しておいて、後からCommandを追加したい場合。
以下サンプル。(いい実装かはわからない)

class ButtonControlVMBase{
    public DelegateCommand SubmitButton_Click_Command { get; set; }

    /// <summary>
    /// SubmitButton_Click 格納プロパティ<br/>
    /// ボタン押下イベントのメソッドを設定します。
    /// デフォルトでは未設定であるというExceptionが発生する<see cref="SubmitButton_Click_Default"/>が実行されます。
    /// </summary>
    public Action SubmitButton_Click_Event { get; set; }

    public ButtonControlVMBase() {
        SubmitButton_Click_Event = SubmitButton_Click_Default;
    }

    /// <summary>
    /// SubmitButton_Click_EventをDelegateCommandに登録します。<br/>
    /// <see cref="SubmitButton_Click_Event"/> に実行したいメソッドを設定した後に呼んでください。
    /// </summary>
    public void SetDelegateCommand() {
        SubmitButton_Click_Command = new DelegateCommand(SubmitButton_Click_Event, Can_Button_Click);
    }

    void SubmitButton_Click_Default() {
        throw new MyHogeHogeException("登録ボタンイベントをプロパティに設定してください。");
    }

    bool Can_Button_Click() {
        return true;
    }
}

あとは、呼び出し側でSubmitButton_Click_Event にメソッドを設定した後、SetDelegateCommand()を読んであげればいい。
上記はもし未設定のままであれば、独自のExceptionが発生するコマンドを登録している。

rakrak

もっとシンプルなら単純にDelegateCommandのプロパティを用意しておけばいい。

class ButtonControlVMBase{
    public DelegateCommand SubmitButton_Click_Command { get; set; }
}

class ButtonControlViewModel : ButtonControlVMBase{
  public ButtonControlViewModel (){
    SubmitButton_Click_Command = new DelegateCommand(Hogehoge);
  }
}

rakrak

バインディングデータの型で表示するコントロールを可変にする方法。

前提

MyUserControlViewModel型のコレクションMyData変数があるとする。
MyDatasの要素には以下3種のViewModelクラスのインスタンスが格納されているものとする。

  • TextViewModel
  • CheckboxViewModel
  • MyUserControlViewModel

要件はこのMyData変数をListViewなどで表示するとき、要素の型によって表示するコントロールを可変としたい。

使用するもの

  • UserControl.Resources (xxx.Resourcesに定義できればOK)
  • UserControl.Resources -> DataTemplate.DataType
    • ここにコード例の通りに対象の型を記載してあげる
  • ItemsControl
  • ContentControl

コード例

<UserControl 
   ~省略~
    xmlns:myvm="clr-namespace:myviewmodel"
    >
  <UserControl.Resources>
        <DataTemplate DataType="{x:Type myvm:TextViewModel}">
            <TextBlock Text={Binding TextData}/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type myvm:CheckboxViewModel}">
            <Checkbox IsChecked="{Binding CheckedFlag}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type myvm:MyUserControlViewModel}">
            <local:MyUserControl DataContext="{Binding}"/>
        </DataTemplate>
    </UserControl.Resources>
   <Grid>
        <ItemsControl ItemsSource="{Binding MyData}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ContentControl Content="{Binding}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>

要約

Resourcesでコントロールの装飾をするとき、装飾対象を型でしていすればいい。
<ContentControl>はすべてのコントロールの親であり、overrideできるようなものという考え。

参考

ListVIewやItemsControlが並べるデータのViewを指定する仕組みについて簡単に解説します。 ItemsControlではItemsSourceに含まれるデータの型によってViewを選択します。 型が指定されており、Keyが指定されていないResourceが対象となります。(<DataTemaplte TargetType = "{x:type hoge}">みたいなやつ)
https://p4j4.hatenablog.com/entry/2020/04/09/173456