Calender

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
2728293031  
<< January 2019 >>

Categories

Archives

Recent Entries

Recent Comment

Recent Trackback

w closet×JUGEM

-

TVアニメ「深夜!天才バカボン」公式サイト

https://shinya-bakabon.com/


iPadから送信
  • 2018.08.11 Saturday
  • 06:54

-

Re[9]: WPFのtreeviewとexpandedイベント

http://bbs.wankuma.com/index.cgi?mode=al2&namber=34832&KLOG=60


iPadから送信
  • 2018.08.11 Saturday
  • 06:52

-

Re[3]: WPFのTreeViewで任意の項目を展開する

@media print { body { margin: 2mm 9mm; } .original-url { display: none; } #article .float.left { float: left !important; } #article .float.right { float: right !important; } #article .float { margin-top: 0 !important; margin-bottom: 0 !important; } } Re[3]: WPFのTreeViewで任意の項目を展開する

C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

VC#2008 WPFアプリケーション

<TreeView Name="treeView">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type c:MyItem}" ItemsSource="{Binding SubItems}">
            <StackPanel Orientation="Horizontal">
                <Rectangle Width="16" Height="16" Fill="Blue" />
                <Label Content="{Binding Name}" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>


class MyItem
{
    public string Name { get; set; }
    public List<MyItem> SubItems { get; private set; }

    public MyItem(string name)
    {
        this.Name = name;
        this.SubItems = new List<MyItem>();
    }
}


MyItem item1 = new MyItem("あああ");
MyItem item2 = new MyItem("いいい");
MyItem item3 = new MyItem("ううう");
MyItem item4 = new MyItem("えええ");

item1.SubItems.Add(item2);
item1.SubItems.Add(item3);
item3.SubItems.Add(item4);
this.treeView.ItemsSource = Enumerable.Repeat(item1, 1);


ツリービューの項目を展開する場合はTreeViewItem.IsExpandedをtrueにすればいいようですが
上記のように項目がTreeViewItemで無い場合はIsExpandedが使えません。
例えばitem3の項目を展開したい場合はどうすればいいのでしょうか。


iPadから送信
  • 2018.08.11 Saturday
  • 04:59

-

WPFやXamarinのICommandを改めて整理する - nuits.jp blog

@media print { body { margin: 2mm 9mm; } .original-url { display: none; } #article .float.left { float: left !important; } #article .float.right { float: right !important; } #article .float { margin-top: 0 !important; margin-bottom: 0 !important; } } WPFやXamarinのICommandを改めて整理する - nuits.jp blog

WPFやXamarinのICommandを改めて整理する

2016.07.10 改定 XamarinにRelayCommand的なデフォルト実装がある事を @qwerty2501 に教えていただき修正しました。
ありがとうございました。

先日(7月7日 木曜日)エクセルソフトさんのビジネスセミナーに行ってきました。

その数日前に
田淵さん:「RelayCommand(DelegateCommandでも可)良く分からない。木曜日に教えて!」
私   :「いいよいいよ、教えるよ」
てな会話をしたのに、セミナー終わったらそのままほったらかして帰ってきてしまったので(ちょっと家庭の事象でw)、その償いに?エントリーとして整理してみることにしました。

なおタイトルにはWPFとXamarinとありますが、UWPとかでも基本は同じだと思います。

と思って書いたんですけど、実は私、みちゃったんですよね。

@amay077 さんに public Command(Action execute, Func<bool> canExecute); を教えていただいた!!!!!Tポイント!!!

— 田淵 義人@エクセルソフト (@ytabuchi)

(´・ω・`)

まぁ書いちゃったから公開するよ。。。

  • ICommandとはなにか
  • ICommandの概要
    • void Execute(object parameter)
    • bool CanExecute(object parameter)
    • event EventHandler CanExecuteChanged;
  • RelayCommand・DelegateCommandとはなにか
  • DelegateCommandの簡単な利用方法

Sponsored Link

ICommandとはなにか

XAMLで発生した何らかの操作に基づき、処理を実行する「コマンド」を表します。
例えば、ButtonのCommandプロパティにICommandの実態をバインドすることで、ボタンをクリックされた際に処理を実行する事が出来ます。
メソッドと何が違うの?というと、以下の点が異なります。

  1. 処理の実行可否を状態として持つ事ができ、また状態の変更を通知できる
  2. 標準の仕組みではXAMLからメソッドの呼び出しはXAMLと同一クラス内のメソッド(コードビハインド)しか利用できないが、ICommandであればバインドが利用できる=別クラス(主にViewModel)の処理を呼び出すことができる

といった特徴があります。
この為、XAMLアーキテクチャで開発する上で、最も重要で基本的な概念の一つと言っても、まぁ誰も怒りはしないと思います。

ICommandの概要

さて、それではICommandをもう少し掘り下げてみてみましょう。

    public interface ICommand
    {
        void Execute(object parameter);
        bool CanExecute(object parameter);
        event EventHandler CanExecuteChanged;
    }

ICommandには3つのメンバが存在します。
順番に説明していきます。

void Execute(object parameter)

コマンドで実行する処理の実体です。
parameterにはXAML側でCommandParameterというのを指定すると渡されてきます。
ただ、これはあくまで私個人の考えですが、ちゃんと必要な値がViewModelにバインドされていれば、どうしてもparameterで渡したいというケースはあまり多くなく、利用頻度は低いです。
CommandParameterがXAML側で指定されていないと、nullが渡ってきます。

bool CanExecute(object parameter)

コマンドが実行可能な状態かどうか判定します。 例えばコマンドがButtonのCommandプロパティにバインドされていた場合、この戻り値がfalseを返却すると、Buttonが非活性状態となります。 parameterには以下略

event EventHandler CanExecuteChanged;

コマンドの状態が変更されたことに伴い、CanExecuteの返す値が変わった場合、このイベントで変更があったことを通知します。
イベントを受信した側は、改めてCanExecuteを実行し、状態の変更に対応します。

例えば、入力フォームがあって、登録ボタンがあったとします。
その際に、氏名が必須だった場合に、氏名が入力されていたら登録ボタンを有効化するといったことを、容易に実現することが可能になるわけです。
慣れると便利ですよ!

RelayCommand・DelegateCommandとはなにか

さて、ここからが本題といえば本題です。
ICommand、非常に便利かつ強力な仕組みなのですが、WPFでもXamarinでも標準では実装が存在しません。
かといって、毎度用途に合わせて実装するのも大変です。

2016.07.10 改定 XamarinにはXamarin.Forms.Commandクラスという同様のデフォルト実装がありましたので、他のMVVMフレームワークを使わない場合はこちらを利用するとよいと思います。

そこでRelayCommandまたはDelegateCommandの登場です。
名前は異なりますが、実際にはほぼ同じものです。
私が見た範囲では当初MSの文献にはRelayCommandという名称で出ていましたが、現在のPrismなどのMVVMフレームワークではDelegateCommandという名称で提供されています。
まあ、本エントリー内ではDelegateCommandという名称で以後は統一しようと思います。

ではDelegateCommandとは何者か?端的に言うと
「コマンドの実行処理をActionとして、実行可否の判定をboolを返却するFuncを渡すことでインスタンス化できるICommand」
です。

通常はPrismとかMVVMLightとかLivetとか、何らかのMVVMフレームワークを使うと思いますが、どのフレームワークでも類する実装が含まれています。
なかなか、口頭では伝えにくいものがありますので、ここからは実際の使い方を見てもらいましょう。

DelegateCommandの簡単な利用方法

では、実際のDelegateCommandの利用方法を見ていきましょう。
作るものは簡単です。

  • 氏名と住所がある登録フォーム
  • 氏名が必須入力で、氏名が入力されないと登録ボタンは押せない

というだけの、しょぼいアプリになります。
実際の動作イメージは以下の通りです。

WPFやXamarinのICommandを改めて整理する

なお、今回のサンプルではPrismのDelegateCommandの実装を利用します。
WPFで試される場合はNuGetから「Pris.WPF」を検索して追加してください。

まずXAMLです。

<Window x:Class="CommandSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CommandSample"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<StackPanel Orientation="Vertical">
<Label Content="氏名"/>
<TextBox Text="{Binding Name}"/>
<Label Content="住所"/>
<TextBox/>
<Button Content="実行" Command="{Binding ExecuteCommand}"/>
<Label Content="{Binding Result}"/>
</StackPanel>
</Window>

氏名・住所のテキストボックスがあって、その下に登録ボタンがあります。
登録ボタンの下には、登録結果を表示するラベルがあります。

よく見ると以下にバインディングが設定されているのが見て取れると思います。

  • 氏名TextBoxのTextプロパティ
  • 登録ButtonのCommandプロパティ
  • 結果LabelのContentプロパティ

ポイントは登録ButtonのCommandプロパティにバインドされているコマンドです。
なお、住所テキストボックスは「在るだけ」ですが、氏名TextBoxからフォーカスアウトしないと、入力値がVMに渡らない為、一身上の都合で無駄に存在しています。

ではViewModelの方を見てみましょう。
全部見ると大きいので、順番に。以下がポイントのDelegateCommandの部分です。

public DelegateCommand ExecuteCommand { get; }
public MainWindowViewModel()
{
ExecuteCommand =
new DelegateCommand(
() => Result = "登録されました",
CanExecute);
}
private bool CanExecute()
{
return !string.IsNullOrWhiteSpace(Name);
}

DelegateCommandをプロパティとして公開し、コンストラクタの中でインスタンス化しています。

引数は二つあります。

一番目の引数はコマンドのExecuteメソッドを呼ばれた場合に呼び出されるActionです。
実際に実行したい処理の内容を記述します。
ここでは、結果ラベルにメッセージを表示しているだけですね。

二番目の引数はコマンドのCanExecuteに該当するFuncを渡します。
ここではコマンドが実行可能な状態かどうかをboolで返してあげます。
ここではメソッドを渡していますが、もちろんラムダ式で記述しても問題ありません。

二番目の引数は省略することも可能で、省略した場合、常に実行可能な状態となります。

ViewModelでもう一つ重要な点が以下です。

private string _name;
public string Name
{
get { return _name; }
set
{
if (OnPropertyChanged(ref _name, value))
ExecuteCommand.RaiseCanExecuteChanged();
}
}

氏名のTextBoxにバインディングしているプロパティです。
Nameプロパティが変更された場合に、先ほどのExecuteCommandに対して、RaiseCanExecuteChangedメソッドを呼び出すことによって、コマンドの実行可能状態が変わったことを通知してあげます。
すると、対象のコマンドをバインドしているコントロールが、改めてコマンドのCanExecuteを呼び、実行可能状態を取得し、結果、Buttonが有効になります。

と言う分けで、今日はここまで。
それではまた!



iPadから送信
  • 2018.08.11 Saturday
  • 04:59

-

WPF の コマンド 引数 を 設定 する 方法 - galife

@media print { body { margin: 2mm 9mm; } .original-url { display: none; } #article .float.left { float: left !important; } #article .float.right { float: right !important; } #article .float { margin-top: 0 !important; margin-bottom: 0 !important; } } WPF の コマンド 引数 を 設定 する 方法 - galife

WPF の コマンド 引数 を 設定 する 方法

基本は xaml で CommandParameter属性 を指定することで引数を渡せます。 以下ではいくつかの サンプルコード を記載しました。

目次

  • 固定値 を 渡す 例
  • 要素 を 渡す 例
    • コントロールを渡す例
    • Windowを渡す例
  • 要素 の プロパティ を 渡す 例

固定値 を 渡す 例

CommandParameter="固定値" を指定します。 ラジオボタンくらいしか使い道が思いつきません…。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

<Window x:Class="WpfApplication.MainView"

        xmlns:vm="clr-namespace:WpfApplication.ViewModels"

        Title="MainWindow"

        Name="window" >

    <Window.DataContext>

        <vm:MainViewModel />

    </Window.DataContext>

    <Grid>

        <RadioButton Content="春"

                     Command="SelectSeason"

                     CommandParameter="1" />

        <RadioButton Content="夏"

                     Command="SelectSeason"

                     CommandParameter="2" />

        <RadioButton Content="秋"

                     Command="SelectSeason"

                     CommandParameter="3" />

        <RadioButton Content="冬"

                     Command="SelectSeason"

                     CommandParameter="4" />

    </Grid>

</Window>

要素 を 渡す 例

渡したい要素に Name属性 を設定し、CommandParameter="{Binding ElementName=要素名}" を指定します。 Name属性 さえ指定していれば良いので、 Control でも Window でも渡せてしまいます。

TextBox コントロール を引数に渡す 例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<Window x:Class="WpfApplication.MainView"

        xmlns:vm="clr-namespace:WpfApplication.ViewModels"

        Title="MainWindow"

        Name="window" >

    <Window.DataContext>

        <vm:MainViewModel />

    </Window.DataContext>

    <Grid>

        <TextBox Text="サンプル"

                 Name="textbox" />

        <Button Content="閉じる"

                Command="Close"

                CommandParameter="{Binding ElementName=textbox}"/>

    </Grid>

</Window>

Window を引数に渡す 例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<Window x:Class="WpfApplication.MainView"

        xmlns:vm="clr-namespace:WpfApplication.ViewModels"

        Title="MainWindow"

        Name="window" >

    <Window.DataContext>

        <vm:MainViewModel />

    </Window.DataContext>

    <Grid>

        <TextBox Text="サンプル"

                 Name="textbox" />

        <Button Content="閉じる"

                Command="Close"

                CommandParameter="{Binding ElementName=window}"/>

    </Grid>

</Window>

要素 の プロパティ を 渡す 例

渡したい要素に Name属性 を設定し、CommandParameter="{Binding ElementName=要素名 Path=プロパティ名}" を指定します。

TextBox の Textプロパティ を引数に渡す 例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<Window x:Class="WpfApplication.MainView"

        xmlns:vm="clr-namespace:WpfApplication.ViewModels"

        Title="MainWindow"

        Name="window" >

    <Window.DataContext>

        <vm:MainViewModel />

    </Window.DataContext>

    <Grid>

        <TextBox Text="サンプル"

                 Name="textbox" />

        <Button Content="閉じる"

                Command="Close"

                CommandParameter="{Binding ElementName=textbox Path=Text}"/>

    </Grid>

</Window>

最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!



iPadから送信
  • 2018.08.11 Saturday
  • 04:59

-

ViewModelをシンプルに書きたい! - コマンド接続編 - - ささいなことですが。

@media print { body { margin: 2mm 9mm; } .original-url { display: none; } #article .float.left { float: left !important; } #article .float.right { float: right !important; } #article .float { margin-top: 0 !important; margin-bottom: 0 !important; } } ViewModelをシンプルに書きたい! - コマンド接続編 - - ささいなことですが。

ViewModelをシンプルに書きたい! - コマンド接続編 -

コマンドはReactiveCommand使えばこんな感じに書けます。シンプルですね。
ishikawa-tatsuya.hatenablog.com

でも、Commandも直接メソッドにつなぎたいんですよ。

前回はそれをイベントでつなぎました。今回はコマンドでつなげてみます。
常にCanExecuteがEnableな時はこんな感じ。コマンドパラメータを受け取る場合はobjectを受け取るという仕様

using Reactive.Bindings;
using System.Reactive.Linq;

namespace WpfApp
{
    public class MainWindowVM
    {
        public ReactiveProperty<string> Number { get; } = new ReactiveProperty<string>("0");
        public void Increment()
        {
            Number.Value = (int.Parse(Number.Value) + 1).ToString();
        }
        public void SetNumber(object param)
        {
            Number.Value = (string)param;
        }
    }
}

Xamlです。{c:Command }というXaml拡張で実現しています。

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:WpfApp"
        xmlns:c="clr-namespace:VVMConnection;assembly=VVMConnection"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <l:MainWindowVM/>
    </Window.DataContext>

    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBox Width="100" Text="{Binding Number.Value, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Content="インクリメント" Command="{c:Command Increment }"/>
            <Button Content="5をパラメータで渡す" Command="{c:Command SetNumber }" CommandParameter="5"/>
        </StackPanel>
    </StackPanel>
</Window>

CanExecuteを使いたい場合はReactivPropertyを指定するという仕様にしました。

using Reactive.Bindings;
using System.Reactive.Linq;

namespace WpfApp
{
    public class MainWindowVM
    {
        public ReactiveProperty<string> Number { get; } = new ReactiveProperty<string>("0");
        public ReactiveProperty<bool> CanIncrement { get; }

        public MainWindowVM()
        {
            int num = 0;
            CanIncrement = Number.Select(e => int.TryParse(e, out num)).ToReactiveProperty();
        }

        public void Increment()
        {
            Number.Value = (int.Parse(Number.Value) + 1).ToString();
        }
    }
}
<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:WpfApp"
        xmlns:c="clr-namespace:VVMConnection;assembly=VVMConnection"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <l:MainWindowVM/>
    </Window.DataContext>

    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBox Width="100" Text="{Binding Number.Value, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Content="インクリメント" Command="{c:Command Increment, CanIncrement }"/>
        </StackPanel>
    </StackPanel>
</Window>

{c:Command}の実装

これもXaml拡張で実現してます。MarkupExtension楽しいですねw

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Markup;

namespace VVMConnection
{
    public class CommandExtension : MarkupExtension
    {
        string _method;
        string _enableProperty;

        public CommandExtension(string method, string enableProperty)
        {
            _method = method;
            _enableProperty = enableProperty;
        }

        public CommandExtension(string method)
        {
            _method = method;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
            if (targetProvider == null)
            {
                return null;
            }
            var target = targetProvider.TargetObject as FrameworkElement;
            if (target == null)
            {
                return null;
            }
            //コマンドを生成
            return new CommandBridge(target, _method, _enableProperty);
        }

        class CommandBridge : ICommand
        {
            public event EventHandler CanExecuteChanged = (_,__)=> { };

            Action _invoke;
            Action<object> _invokeParam;
            dynamic _enable;

            internal CommandBridge(FrameworkElement target, string method, string enableProperty)
            {
                var connect = MakeConnectAction(target, method, enableProperty);
                connect();
                target.DataContextChanged += (_, __) => connect();
            }

            public bool CanExecute(object parameter)=> _enable == null ? true : _enable.Value;

            public void Execute(object parameter)
            {
                if (_invoke != null) _invoke();
                else _invokeParam(parameter);
            }

            Action MakeConnectAction(FrameworkElement target, string method, string enableProperty)
            {
                return () =>
                {
                    var vm = target.DataContext;
                    if (vm == null)
                    {
                        return;
                    }

                    //Executeで呼び出す実行メソッド取得
                    var vmType = vm.GetType();
                    var methodInfo = vmType.GetMethod(method);
                    if (methodInfo == null)
                    {
                        return;
                    }
                    var args = methodInfo.GetParameters();
                    if (args.Length == 0)
                    {
                        //引数なし
                        _invoke = () => methodInfo.Invoke(vm, new object[0]);
                    }
                    else if (args.Length == 1 && args[0].ParameterType == typeof(object))
                    {
                        //パラメータを受け取る
                        _invokeParam = p => methodInfo.Invoke(vm, new object[] );
                    }
                    else
                    {
                        return;
                    }
                    if (string.IsNullOrEmpty(enableProperty))
                    {
                        return;
                    }

                    //CanExecuteは
                    //INotifyPropertyChangedを実装していて
                    //bool Valueというプロパティーがあるオブジェクトで指定
                    //ReactivePropertyでもいいし、そんな感じのクラスならOKってことにしました。
                    var enablePropertyInfo = vmType.GetProperty(enableProperty);
                    if (enablePropertyInfo == null)
                    {
                        return;
                    }

                    var enable = enablePropertyInfo.GetValue(vm) as INotifyPropertyChanged;
                    if (enable == null)
                    {
                        return;
                    }
                    Type enableType = enable.GetType();
                    var valueProperty = enableType.GetProperty("Value");
                    if (valueProperty == null || valueProperty.PropertyType != typeof(bool))
                    {
                        return;
                    }
                    enable.PropertyChanged += (_, __) => CanExecuteChanged(this, EventArgs.Empty);
                    _enable = enable;
                };
            }
        }
    }
}


iPadから送信
  • 2018.08.11 Saturday
  • 04:59

-

C#のWPFでTreeViewへデータをバインドする - Ararami Atudio

@media print { body { margin: 2mm 9mm; } .original-url { display: none; } #article .float.left { float: left !important; } #article .float.right { float: right !important; } #article .float { margin-top: 0 !important; margin-bottom: 0 !important; } } C#のWPFでTreeViewへデータをバインドする - Ararami Atudio

C#のWPFでTreeViewへデータをバインドする

ツリー構造を展開したり折りたたんだりして表示できるTreeViewコントロール。

このTreeViewへデータをバインドするには、ItemTemplateへHierarchicalDataTemplateを定義して使う。

バインドさせるデータを格納するクラス

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

public class TreeSource : INotifyPropertyChanged

{

    private bool                             _IsExpanded = true;

    private string                           _Text       = "";

    private TreeSource                       _Parent     = null;

    private ObservableCollection<TreeSource> _Children   = null;

    public bool IsExpanded

    {

        get { return _IsExpanded; }

        set { _IsExpanded = value; OnPropertyChanged("IsExpanded"); }

    }

    public string Text

    {

        get { return _Text; }

        set { _Text = value; OnPropertyChanged("Text"); }

    }

    public TreeSource Parent

    {

        get { return _Parent; }

        set { _Parent = value; OnPropertyChanged("Parent"); }

    }

    public ObservableCollection<TreeSource> Children

    {

        get { return _Children; }

        set { _Children = value; OnPropertyChanged("Childen"); }

    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string name)

    {

        if (null == this.PropertyChanged) return;

        this.PropertyChanged(this, new PropertyChangedEventArgs(name));

    }

    public void Add(TreeSource child)

    {

        if (null == Children) Children = new ObservableCollection<TreeSource>();

        child.Parent = this;

        Children.Add(child);

    }

}

プロパティ変更が正しく通知されるようINotifyPropertyChangedインターフェースを実装。

IsExpandedプロパティ

展開されているか折りたたまれているかを格納。

Textプロパティ

表示する文字列。

 

Childrenプロパティ

子要素となるデータを格納する。

Parentプロパティ

親要素を格納する事で階層をさかのぼって参照出来るようにしている。

Parentプロパティを正しくセットする為、Addメソッドを作っている。(42〜47行目)

XAML

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

<Window x:Class="WpfApplication1.MainWindow"

        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:WpfApplication1"

        mc:Ignorable="d"

        Title="MainWindow" Height="200" Width="250">

    <Grid Margin="20">

        <TreeView ItemsSource="{Binding TreeRoot}">

            <TreeView.Resources>

                <Style TargetType="TreeViewItem">

                    <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/>

                </Style>

            </TreeView.Resources>

            <TreeView.ItemTemplate>

                <HierarchicalDataTemplate DataType="{x:Type local:TreeSource}" ItemsSource="{Binding Children}">

                    <TextBlock Text="{Binding Text}"/>

                </HierarchicalDataTemplate>

            </TreeView.ItemTemplate>

        </TreeView>

    </Grid>

</Window>

IsExpandedをバインド (12〜14行目)

TreeViewItemのStyle定義を追加して、TreeViewItemのIsExpandedプロパティへTreeSourceのIsExpandedプロパティをバインドする。

HierarchicalDataTemplateを定義 (16から20行目)

TreeView.ItemTemplateをつかって、HierarchicalDataTemplateを定義する。

HierarchicalDataTemplateのDataTypeにデータの型をセット。

HierarchicalDataTemplateのItemsSourceには子要素となるChildrenプロパティをバインドする。

HierarchicalDataTemplate内にTextBlockを配置してTextプロパティを表示させる。

本体のソース

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

public partial class MainWindow : Window

{

    public ObservableCollection<TreeSource> TreeRoot { get; set; }

    public MainWindow()

    {

        InitializeComponent();

        TreeRoot = new ObservableCollection<TreeSource>();

        var item1  = new TreeSource() { Text = "Item1", IsExpanded = true };

        var item11 = new TreeSource() { Text = "Item1-1", IsExpanded = true };

        var item12 = new TreeSource() { Text = "Item1-2", IsExpanded = true };

        var item2  = new TreeSource() { Text = "Item2", IsExpanded = false };

        var item21 = new TreeSource() { Text = "Item2-1", IsExpanded = true };

        TreeRoot.Add(item1);

        TreeRoot.Add(item2);

        item1.Add(item11);

        item1.Add(item12);

        item2.Add(item21);

        DataContext = this;

    }

}



iPadから送信
  • 2018.08.10 Friday
  • 19:07

-

キン肉マン マッスルショップ&#169;YUDETAMAGO in 東急ハンズ三宮店 - 店舗のイチオシ - 東急ハンズ三宮店

@media print { body { margin: 2mm 9mm; } .original-url { display: none; } #article .float.left { float: left !important; } #article .float.right { float: right !important; } #article .float { margin-top: 0 !important; margin-bottom: 0 !important; } } キン肉マン マッスルショップ©YUDETAMAGO in 東急ハンズ三宮店 - 店舗のイチオシ - 東急ハンズ三宮店

キン肉マン マッスルショップ©YUDETAMAGO in 東急ハンズ三宮店

店舗のイチオシ

キン肉マン マッスルショップ©yudetamago in 東急ハンズ三宮店

7月30日(月)〜8月22日(水)

へのつっぱりはいらんですよ!
心に愛があればみんなスーパーヒーロー!!
今回はキン肉マンマッスルショップだけじゃない!
SOLDIER TEAM BASE、2世「NEW GENERATION」のグッズも大集合。

おなじみのアイドル超人はもちろん、今また熱い運命の5王子、
大人気の「ソルジャーチーム」、息子であるキン肉万太郎を始め2世超人も揃います。
新作商品、 先行販売商品、 Tシャツ、雑貨、フィギュア・・・
肉ゴコロを満たすイベントに、どうぞご来店ください!



iPadから送信
  • 2018.08.10 Friday
  • 07:08

-

WPF TreeViewを使ってみた(2)

@media print { body { margin: 2mm 9mm; } .original-url { display: none; } #article .float.left { float: left !important; } #article .float.right { float: right !important; } #article .float { margin-top: 0 !important; margin-bottom: 0 !important; } } WPF TreeViewを使ってみた(2)

WPF TreeViewを使ってみた(2)

前回はTreeViewの簡単な実装だけやってみた
今回は要素の取得まで実装してみたが、
取得方法を調べたり考えたりしてたら前回の実装方法と大分変わってしまった

作るもの

前回と同じような感じだが、エクスプローラー的なものにしてみる
2017-04-28_23h25_10.png

作ってみる

前回同様MVVM的に作る
※ReactivePropertyを使用
(参考:かずきのBlog@hatena/ReactiveProperty オーバービュー)

考えてみる

まずTreeViewのSelectedItemとかにプロパティをBindすればいいじゃん、と思ったら読み取り専用でBindできないことが判明
それじゃどうするか
TreeViewItemを継承させてSelectedイベントに選択したものを取得する処理を追加したらいいのでは?
ってことでやってみる

Model

まずはModel
といっても今回はこれがほぼすべて

public class Model_TreeViewItem:TreeViewItem
    {
        public DirectoryInfo _Directory { get; set; }
        private bool _Expanded { get; set; } = false;
        public ReactiveProperty<Model_TreeViewItem> _SelectionItem { get; set; } = new ReactiveProperty<Model_TreeViewItem>();

        public Model_TreeViewItem(string path)
        {
            this._Directory = new DirectoryInfo(path);
            if (_Directory.GetDirectories().Count() > 0)
            {
                this.Items.Add(new TreeViewItem());
                this.Expanded += Model_TreeViewItem_Expanded;
            }
            this.Header = CreateHeader();
            this.Selected += Model_TreeViewItem_Selected;
        }

        private void Model_TreeViewItem_Expanded(object sender, RoutedEventArgs e)
        {
            if (!_Expanded)
            {
                this.Items.Clear();
                foreach (DirectoryInfo dir in _Directory.GetDirectories())
                {
                    if (dir.Attributes == FileAttributes.Directory)
                    {
                        this.Items.Add(new Model_TreeViewItem(dir.FullName));
                    }
                }
                _Expanded = true;
            }
        }

        private StackPanel CreateHeader()
        {
            StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal };
            sp.Children.Add(new Image()
            {
                Source = new BitmapImage(new Uri(@"Resources\Folder.ico", UriKind.Relative)),
                Width = 15,
                Height = 18,
            });
            sp.Children.Add(new TextBlock() { Text = _Directory.Name });
            return sp;
        }

        private void Model_TreeViewItem_Selected(object sender, RoutedEventArgs e)
        {
            _SelectionItem.Value = (this.IsSelected) ? this : (Model_TreeViewItem)e.Source ;
        }
    }

いきなり長めのコードになってしまったので細かく解説する

前述したようにTreeViewItemを継承している
プロパティは以下の通り

_Directory
自身のフォルダ
_Expanded
一度でも展開したかどうか
_SelectionItem
選択しているオブジェクト

まず、コンストラクタで渡されたパスをもとにDirectoryInfoを作って、そのフォルダにサブフォルダがあるかを調べている
一度にすべてのフォルダを走査して子要素を追加すると処理に時間がかかってしまうのでここでは1階層下のもののみ走査している
子要素があるフォルダになにも追加しないと展開する▷マークが表示されないので、サブフォルダがあるものはItemsにダミーのTreeItemを追加している

そんでExpandedイベントでサブフォルダを取得してそれぞれをnewした自身のクラスをItemに追加している

今回はエクスプローラー風にフォルダ名の左にアイコンをつけたかったのでHeaderプロパティにStackPanelをセットしている
詳細は省く

あと、Selectedイベント
こいつが曲者でハマってしまった
こいつは子要素側で発生しても親要素に伝播して親要素でも同じイベントが発生する
なので単純に(Model_TreeViewItem)e.Sourceとやっただけでは取得できない
考えた結果、この伝播する動作を利用してルートの親要素にイベント発生源のオブジェクトを運ぶことにした
_SelectionItem.Value = (this.IsSelected) ? this : (Model_TreeViewItem)e.Source ;
では、_SelectionItemプロパティに、自身が選択状態であれば自身を、そうでなければイベント発生源をセットしている
これでルートの_SelectionItemプロパティにBindすれば選択しているオブジェクトが取得できるようになった

ViewModel

シンプル
ただのViewModel
Modelのコンストラクタにパスを渡してるだけ

    class ViewModel
    {
        public List<Model_TreeViewItem> VM { get; }

        public ViewModel()
        {
            VM = new List<Model_TreeViewItem>()
            {
                new Model_TreeViewItem(@"C:\Users\yoshiki\Desktop\てすと")
            };
        }
    }

View

Modelのおかげでこんだけで済む

        <TreeView x:Name="treeView" ItemsSource="{Binding VM}">

選択中のDirectoryはこんな感じでBindできる

<TextBlock Text="{Binding VM/_SelectionItem.Value._Directory.Name}" />

おまけ

ListViewにファイル一覧をアイコン付きで表示してみる場合
Converterを使えばよさげ
_SelectionItem.Value._DirectoryをBindしてConvertメソッドで受け取る

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DirectoryInfo dir = (DirectoryInfo)value;
            List<StackPanel> list = new List<StackPanel>();
            foreach(var file in dir.GetFiles())
            {
                StackPanel sp = new StackPanel() { Orientation = Orientation.Horizontal };
                System.Drawing.Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(file.FullName);
                sp.Children.Add(new Image()
                {
                    Source= Imaging.CreateBitmapSourceFromHIcon(icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()),
                    Width = 15,
                    Height = 18,
                });
                sp.Children.Add(new TextBlock()
                {
                    Text = file.Name
                });
                list.Add(sp);
                icon.Dispose();
            }
            return list;
        }

ざっくり解説すると、受け取ったDirectoryInfo内のファイルを取得し、それぞれの関連付けられたアイコンとファイル名をStackPanelに詰め込んでそれをリストにして返している

まとめ

自分なりに考えて実装してみたら少しややこしくなった
思い付きでやってみたため間違っている可能性があるかも
この実装方法が正しいかはわからないが、Bindしたりするのはかなり楽になったと思う



iPadから送信
  • 2018.08.09 Thursday
  • 19:16