Calender

S M T W T F S
    123
45678910
11121314151617
18192021222324
252627282930 
<< November 2018 >>

Categories

Archives

Recent Entries

Recent Comment

Recent Trackback

w closet×JUGEM

-

キン肉マン マッスルショップ&#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

-

121ware.com > サービス&サポート > Q&A > Q&A番号 013672

@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; } } 121ware.com > サービス&サポート > Q&A > Q&A番号 013672

ペイントで作成した画像をアイコンファイルとして保存し、任意のファイルやフォルダーのアイコンに設定することができます。

はじめに

Windows 7では、ファイルやフォルダーなどのアイコンを、好みのアイコンに変更できます。
Windows 7には複数のアイコンが用意されていますが、ペイントを使用して独自のアイコンを作成し、変更することができます。

  • ペイントでアイコン画像を保存する際は、「.ico」形式で保存する必要があります。
  • 一部のアイコンについては、初期状態のアイコンから変更できないことがあります。

操作手順

Windows 7のペイントでオリジナルのアイコンを作成するには、以下の操作手順を行ってください。

以下の項目を順に確認してください。

  • 1. ペイントでアイコンを作成する方法
  • 2. アイコンを変更する方法

1. ペイントでアイコンを作成する方法

ペイントでアイコンを作成するには、以下の操作手順を行ってください。

  1. 「スタート」→「すべてのプログラム」→「アクセサリ」→「ペイント」の順にクリックします。
    「スタート」→「すべてのプログラム」→「アクセサリ」→「ペイント」の順にクリックします

  2. 「ペイント」が起動します。
    リボンから「ホーム」タブをクリックし、「イメージ」グループの「サイズ変更」をクリックします。
    リボンから「ホーム」タブをクリックし、「イメージ」グループの「サイズ変更」をクリックします

  3. 「サイズ変更と傾斜」が表示されます。
    「サイズ変更」欄から「ピクセル」をクリックし、「水平方向」と「垂直方向」ボックスに等しい値を入力して「OK」をクリックします。
    ここでは例として、「水平方向」と「垂直方向」ボックスに「320」と入力します。
    「サイズ変更」欄から「ピクセル」をクリックし、「水平方向」と「垂直方向」ボックスに等しい値を入力して「ok」をクリックします

  4. アイコン画像を作成します。
    ここでは例として、以下のような画像を作成します。
    アイコン画像を作成します

  5. リボンから「ホーム」タブをクリックし、「イメージ」グループの「サイズ変更」をクリックします。
    リボンから「ホーム」タブをクリックし、「イメージ」グループの「サイズ変更」をクリックします

  6. 「サイズ変更と傾斜」が表示されます。
    「サイズ変更」欄から「パーセント」をクリックし、アイコンの画像が「32×32ピクセル」となるよう縮小率を入力して「OK」をクリックします。
    ここでは例として、「水平方向」と「垂直方向」ボックスに「10」と入力します。
    「サイズ変更」欄から、アイコンの画像が「32×32ピクセル」となるよう縮小率を入力して「ok」をクリックします

  7. リボンから「ペイント」をクリックし、表示された一覧から「名前を付けて保存」にマウスポインターを合わせて「BMP 画像」をクリックします。
    リボンから「ペイント」をクリックし、表示された一覧から「名前を付けて保存」にマウスポインターを合わせて「bmp 画像」をクリックします

  8. ファイルの保存場所を指定して、「ファイル名」ボックスに任意の名前を入力し、ファイル名の後に「.ico」を入力します。
    「ファイルの種類」ボックスから「24ビットビットマップ」をクリックして「保存」をクリックします。
    ここでは例として、「ピクチャ」フォルダーに「Test.ico」という名前で保存します。
    ファイルの保存場所を指定して、「ファイル名」ボックスに任意の名前を入力し、ファイル名の後に「.ico」を入力します。「ファイルの種類」ボックスから「24ビットビットマップ」をクリックして「保存」をクリックします

以上で操作完了です。

作成したアイコンに変更するには、「2. アイコンを変更する方法」を行います。

2. アイコンを変更する方法

アイコンを変更するには、以下の操作手順を行ってください。
ここでは例として、フォルダーのアイコンを「1. ペイントでアイコンを作成する方法」で作成したアイコンに変更します。

  1. アイコンを変更したいフォルダーを右クリックし、表示された一覧から「プロパティ」をクリックします。
    ここでは例として、デスクトップ上に作成した「オリジナル」フォルダーを右クリックします。
    アイコンを変更したいフォルダーを右クリックし、表示された一覧から「プロパティ」をクリックします

  2. 「(フォルダー名)のプロパティ」が表示されます。
    「カスタマイズ」タブをクリックし、「アイコンの変更」をクリックします。
    「カスタマイズ」タブをクリックし、「アイコンの変更」をクリックします

  3. 「(フォルダー名)フォルダーのアイコンの変更」が表示されます。
    「参照」をクリックします。
    「参照」をクリックします

  4. 変更したいアイコンファイル(「.ico」形式)を選択し、「開く」をクリックします。
    ここでは例として、「Test.ico」ファイルを開きます。
    変更したいアイコンファイル(「.ico」形式)を選択し、「開く」をクリックします

  5. 「OK」をクリックします。
    「ok」をクリックします

  6. 「(フォルダー名)のプロパティ」画面に戻ったら、「OK」をクリックします。
    「(フォルダー名)のプロパティ」画面に戻ったら、「ok」をクリックします

以上で操作完了です。

フォルダーのアイコンが変更されたことを確認してください。
フォルダーのアイコンが変更されたことを確認してください

補足

変更したアイコンを元のアイコンに戻す場合は、手順3で「既定値に戻す」をクリックして「OK」をクリックします。
「既定値に戻す」をクリックして「ok」をクリックします

このQ&Aに出てきた用語



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

-

Setting up WPF treeview triggers to show different images on expand - Stack Overflow

@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; } } Setting up WPF treeview triggers to show different images on expand - Stack Overflow

Setting up WPF treeview triggers to show different images on expand

<UserControl.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="../Theme.xaml" /> </ResourceDictionary.MergedDictionaries> <HierarchicalDataTemplate DataType="{x:Type vm:SolutionsViewModel}" ItemsSource="{Binding Items}"> <StackPanel Orientation="Horizontal"> <Image x:Name="nodeImg" Width="16" Height="16" Source="pack://siteOfOrigin:,,,/Resources/FolderClosed.bmp"/> <TextBlock Margin="5,0,0,0" Text="{Binding Name}" /> </StackPanel> <HierarchicalDataTemplate.Triggers> <DataTrigger Binding="{Binding IsExpanded}" Value="True"> <Setter TargetName="nodeImg" Property="Source" Value="pack://siteOfOrigin:,,,/Resources//FolderOpen.bmp"/> </DataTrigger> </HierarchicalDataTemplate.Triggers> </HierarchicalDataTemplate> <HierarchicalDataTemplate DataType="{x:Type sol:Solution}" ItemsSource="{Binding Items}"> <StackPanel Orientation="Horizontal"> <Image x:Name="treeImg" Width="16" Height="16" Source="pack://siteOfOrigin:,,,/Resources/SolutionClosed.bmp"/> <TextBlock Margin="5,0,0,0" Text="{Binding Name}" /> </StackPanel> <HierarchicalDataTemplate.Triggers> <DataTrigger Binding="{Binding IsExpanded}" Value="True"> <Setter TargetName="treeImg" Property="Source" Value="pack://siteOfOrigin:,,,/Resources//SolutionOpen.bmp"/> </DataTrigger> </HierarchicalDataTemplate.Triggers> </HierarchicalDataTemplate> <HierarchicalDataTemplate DataType="{x:Type vm:ConventionsViewModel}" ItemsSource="{Binding Items}"> <StackPanel Orientation="Horizontal"> <Image x:Name="nodeImg" Width="16" Height="16" Source="pack://siteOfOrigin:,,,/Resources/FolderClosed.bmp"/> <TextBlock Margin="5,0,0,0" Text="{Binding Name}" /> </StackPanel> <HierarchicalDataTemplate.Triggers> <DataTrigger Binding="{Binding IsExpanded}" Value="True"> <Setter TargetName="nodeImg" Property="Source" Value="pack://siteOfOrigin:,,,/Resources//FolderOpen.bmp"/> </DataTrigger> </HierarchicalDataTemplate.Triggers> </HierarchicalDataTemplate> </ResourceDictionary> </UserControl.Resources> <UserControl.DataContext> <ObjectDataProvider ObjectType="{x:Type vm:TreeViewModel}" MethodName="CreateDefaultTree" /> </UserControl.DataContext> <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"> <Grid> <TreeView Name="solutionsModel" ItemsSource="{Binding Items}"> <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> <Setter Property="FontWeight" Value="Normal" /> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="FontWeight" Value="Bold" /> </Trigger> </Style.Triggers> </Style> </TreeView.ItemContainerStyle> </TreeView> </Grid> </ScrollViewer>


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

-

GitHub - dev4sys/PsTreeFolderBrowser: A TreeView in PowerShell

@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; } } GitHub - dev4sys/PsTreeFolderBrowser: A TreeView in PowerShell

dev4sys/PsTreeFolderBrowser

README.md



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

-

新しい競馬必勝法を着想

通勤の時、新しい競馬必勝法の&#127943;着想を得た。
みっつの必勝法で当て残しナシ!

iPadから送信
  • 2018.08.09 Thursday
  • 08:44

-

4級 | 自然環境研究センター

@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; } } 4級 | 自然環境研究センター

4級

学校検定については、こちら(学校検定)をご覧ください。

3級、4級の第1回・第2回試験に合格された方に、「生物分類技能検定オリジナル野帳」をプレゼントします。
3級、4級第1回・第2回試験合格者プレゼントについて


受験資格・受験対象者

  • 受験資格:なし
  • 受験対象者:生物一般に興味がある方

出題形式・出題範囲

  • 出題形式:択一問題(マークシート)と、実物標本などを用いたスケッチ。
  • 出題範囲:広く生物一般を対象とした問題が出題されます。
    身近な生物(野生動植物種、栽培種、家畜、野菜、果物など)の区別や形に関する基礎的問題、スケッチなどが出題されます。

合格基準点

100点満点で60点

受験の流れ

受験申込上の注意事項や受験の手引きをご確認のうえお申し込みください。
受験申込上の注意事項  受験の手引き(1〜4級共通)(PDF:2.7MB)


インターネットによる申込

受付期間:7月1日(日)〜9月14日(金)

  1. 申込の流れ

    申込の流れ

  2. 受験申込登録

    受験申込入力フォームに申込情報や支払先等を入力してください。
    「仮登録完了」画面には払い込み時に必要な情報がございますので、必ずプリントアウトするか、メモをお取りください。
    ※受験申込入力はPCをご利用ください。携帯電話からのお申し込みは出来ません。
    ※「仮登録完了」画面と同様の内容がご入力いただいたEメールアドレス(携帯電話メールアドレス不可)に自動送信されますが、遅延やメールアドレスの間違いなどで届かない場合がございます。

  3. 受験手数料の払い込み
    • 受験手数料:3,080円
    • コンビニペーパーレス決済(支払手数料無料)です。以下のコンビニエンスストアから支払先を選び、受験手数料をお支払いください。
      • セブンイレブン、ローソン、ファミリーマート、サークルKサンクス、ミニストップ、デイリーヤマザキ、ヤマザキデイリーストア、セイコーマート
    • 受験手数料のお支払いをもって、「受験申込」が完了します。「受験申込の完了」の連絡は、「受験票」の送付をもってかえさせていただきます。
  4. 受験票の受取
    • 試験日2週間前に発送します。
    • 受け取ったらすぐに開封し、受験級、試験会場、時間、持ち物、裏面の注意事項等を確認してください。
    • 試験日の1週間前の時点で届いていない場合は、検定事務局にお問い合わせください。
  5. 試験の実施
    • 受験票を忘れた場合は、受験できないことがあります。
    • 試験会場に関するお問い合わせは、検定事務局へお願いいたします。
  6. 結果通知の受取
    • 試験結果(合否および得点)は検定委員会による審査の後、郵送にて直接ご本人宛に通知いたします。
      合格者へは認定証が同封されます。認定証見本(PDF:96KB)
      ※結果通知発送後、ホームページに合格者の受験番号と正解・配点を約1ヶ月間掲載します。

インターネットによる申込に進む方はこちら

4級インターネット受験申込入口(申込受付は7月1日からです)



郵送による申込

受付期間:7月1日(日)〜9月14日(金)当日消印有効

  1. 受験申込書の入手
  2. 受験申込書の記入
    • 記入例を確認のうえ、申込書に必要事項をもれなく記入し、押印してください。
  3. 受験手数料の払い込み
    • 受験手数料:3,080円
    • 郵便局にて、備え付けの青色払込用紙を使用し、受験手数料をお支払いください。 払込手数料は申込者のご負担となりますので、あらかじめご了承ください。

    手数料の払込先

  4. 必要書類の送付
    • 郵便事故防止のため、郵便局の窓口で簡易書留としてお出しください。
  5. 受験票の受取
    • 試験日2週間前に発送します。
    • 受け取ったらすぐに開封し、受験級、試験会場、時間、持ち物、裏面の注意事項等を確認してください。
    • 試験日の1週間前の時点で届いていない場合は、検定事務局にお問い合わせください。
  6. 試験の実施
    • 受験票を忘れた場合は、受験できないことがあります。
    • 試験会場に関するお問い合わせは、検定事務局へお願いいたします。
  7. 結果通知の受取
    • 試験結果(合否および得点)は検定委員会による審査の後、郵送にて直接ご本人宛に通知いたします。
      合格者へは認定証が同封されます。認定証見本(PDF:96KB)
      ※結果通知発送後、ホームページに合格者の受験番号と正解・配点を約1ヶ月間掲載します。

日程(受付期間、試験日時、開催地・会場、結果通知等)

平成30年度

受付期間 インターネット:7月1日(日)〜9月14日(金)
郵送:7月1日(日)〜9月14日(金)当日消印有効
*郵送取り寄せは、申込受付終了日の1週間前までです
申込書ダウンロード期間 7月1日(日)〜9月13日(木)
受験票の発送 10月26日(金)
試験日 11月11日(日)
試験時間 10:00〜12:00(120分間)
試験会場 札幌:酪農学園大学
東京:青山学院大学青山キャンパス
大阪:大和大学
福岡:福岡県中小企業振興センター
結果通知 平成31年1月31日(木)

お問い合わせ

一般財団法人自然環境研究センター 生物分類技能検定事務局
〒130-8606 東京都墨田区江東橋3丁目3番7号
TEL:03-6659-6110(平日9時30分〜17時30分)  FAX:03-6659-6320(24時間受付)
※ 検定事務局への直接のお越しはご遠慮ください。



iPadから送信
  • 2018.08.09 Thursday
  • 08:43

-

【C#】ReactiveProperty全然分からねぇ!って人向けのFAQ集【修正済】

@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#】ReactiveProperty全然分からねぇ!って人向けのFAQ集【修正済】

【C#】ReactiveProperty全然分からねぇ!って人向けのFAQ集【修正済】

概要

 C#でMVVMパターンを組んで開発しようとした際に役に立つライブラリの一つにReactivePropertyがあります。
 ただ、これを使って書くと、従来とは記述が大きく変わってしまいますので、それなりに慣れが必要となります。
 以下、そんなReactivePropertyの使い方についてのFAQ集となります(自分が躓いたところばかりなので実質備忘録)。

※2017/12/31追記:ReactivePropertyのメンテナーの一人でいらっしゃるかずき(Kazuki Ota)さんから指摘を賜り、記事を大幅に修正しました。

FAQ集

Q. 具体的にはどう便利なの?

  • INotifyPropertyChangedインターフェースを実装したり、そうした通知機能がコミコミのクラスを継承したりする必要がない。要するに 記述量が少なくて済む
  • 「値が変更された時にアクションを起こす」「ボタンを押すなどの操作をした時にアクションを起こす」ことを、Subscribeメソッドで購読することにより表現できる。要するに setアクセサーにごちゃごちゃ書かなくていい
  • 入力した値のチェック機能 がある。つまり、「入力内容が全て正常な場合のみ、入力ボタンを有効にする」などといった処理を楽に書くことができる
/* --------------- */
/* |従来の記述方法| */
/* --------------- */
using System.ComponentModel;
class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string parameter)
     => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
}
class ViewModel : ViewModelBase 
{
    private int selectedMode;
    public int SelectedMode {
        get{ return selectedMode; }
        set{
            if(value == selectedMode)
                return;
            selectedMode = value;
            DoFunc(selectedMode);
            NotifyPropertyChanged(nameof(SelectedMode));
        }
    }
}

/* --------------------------------- */
/* |ReactivePropertyにおける記述方法| */
/* --------------------------------- */
using System;
using System.ComponentModel;
using Reactive.Bindings;
class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    public ReactiveProperty<int> SelectedMode {get;} = new ReactiveProperty<int>();

    // コンストラクタ内で
    SelectedMode.Subscribe(x => DoFunc(x));
}

Q. あれ、ReactivePropertyだとINotifyPropertyChanged要らないんじゃなかったっけ?

 確かに書かなくてもプログラムは動きます。ただ、Visual C#の仕様上、上記のように継承していないと メモリーリークの可能性があります。……アイエエエ!?ナンデ!?メモリーリークナンデ!?

 参考にすべき資料:
  【WPF】ViewModelがINotifyPropertyChangedを実装していないとメモリリークする件 - aridai.NET
  MVVMでメモリリークしちゃってました 原因と対策編 - かずきのBlog@hatena

 また、メモリーリーク絡みで言いますと、ReactivePropertyReactiveCommandなどはIDisposableを継承していますので、使用後はDisposeする必要があります。単独で自己完結しているような型ならGC任せでも良いのですが、後述の質問「Q. 既存のModel・ViewModelを全部書き換えるの辛くない?」のように他のオブジェクトがイベントによって紐付けられている場合、Disposeしておくことが望ましいでしょう。具体的にはこんな感じ。

class ViewModel : INotifyPropertyChanged, IDisposable {
    public event PropertyChangedEventHandler PropertyChanged;
    // Disposeが必要なReactivePropertyやReactiveCommandを集約させるための仕掛け
    private CompositeDisposable Disposable { get; } = new CompositeDisposable();
    // ReactivePropertyやReactiveCommandを用意する
    public ReactiveProperty<int> Hoge{ get; }
    public ReactiveCommand Fuga{ get; };
    public ViewModel(){
        // AddToメソッドでDisposeしたいオブジェクトをDisposableプロパティ(の実体)に登録する
        this.Hoge = new ReactiveProperty<int>().AddTo(this.Disposable);
        this.Fuga = new ReactiveCommand ().AddTo(this.Disposable);
    }
    public void Dispose(){
        // まとめてDisposeする
        Disposable.Dispose();
    }
}

 参考資料:
  ReactivePropertyの後始末 - かずきのBlog@hatena

Q. ReactiveProperty<T>はどう記述すればいいの?

 ReactiveProperty<T>型はT型の変数を入れる箱のようなもの。
 通知機能を内包しているのでそのままXAMLにBindingできるし、値の変更を反映させるのも簡単。
 なお、ReactiveProperty<T>型からT型の値を取り出す際は、Valueプロパティを参照する(C#側でもXAML側でも同様)。

C#側の記述.cs

class ViewModel : INotifyPropertyChanged{
    // プロパティ
    // 宣言と同時に初期化 or コンストラクタで初期化する場合は「private set;」の記述不要
    public ReactiveProperty<int> SelectedMode { get; private set; }

    // メソッド内での記述
    //デフォルト値で初期化
    SelectedMode = new ReactiveProperty<int>();
    //指定した数値(この場合は3)で初期化
    SelectedMode = new ReactiveProperty<int>(3);
    // 値を引き出す・書き換える際はValueプロパティを参照すること
    int selectedMode = SelectedMode.Value;
    SelectedMode.Value = 2;
}

XAML側の記述.xml

<Combobox SelectedIndex = {Binding SelectedMode.Value, Mode=TwoWay}>

Q. ReactiveProperty<T>の値が変更した時の動作はどう記述すればいいの?

 Subscribeメソッドを使う。これの引数にIObserver<T>を渡すことで購読できるので、「値が変更した際にはこういったアクションを起こす」といったことをシンプルに記述できる。
 なお、以下の例ではラムダ式を渡しているが、using System;しないとラムダ式をIObserver<T>に変換できないので注意!

using System;
//値が変更された時の動作
SelectedMode.Subscribe(_ => DoFunc(SelectedMode.Value));
//値が変更された時の動作(色々行うので{}が必要)
SelectedMode.Subscribe(_ => {
    DoFunc1(SelectedMode.Value);
    DoFunc2(SelectedMode.Value);
    DoFunc3(SelectedMode.Value);
});

Q. ReadOnlyReactivePropertyって何?

 まず、「ReactiveProperty<T>は他のReactiveProperty<T>から作成できる」ことを説明しておきましょう。例えば「あるTextBoxの中身を書き換えると、それを加工した結果がTextBlockに表示される」ような動きが可能になります。要するに連動ですね。

using System.Reactive.Linq;
// 変数を宣言
public ReactiveProperty<int> Input1 { get; } = ReactiveProperty<int>();
public ReactiveProperty<int> Input2 { get; } = ReactiveProperty<int>();
public ReactiveProperty<int> Output1 { get; }
public ReactiveProperty<bool> Output2 { get; }
public ReactiveProperty<string> Output3 { get; }

// 定義する際に、どう加工するかをメソッドチェーンで示す
// (Output1は、Input1を2倍にした数値となる)
Output1 = Input1.Select(x => x * 2).ToReactiveProperty();
// この「Select」は、普通のLINQと同様、型変換にも使える
// (Output2は、Input1が偶数ならtrue、機数ならfalseとなる)
Output2 = Input1.Select(x => (x % 2 == 0)).ToReactiveProperty();
// 2つ以上のReactivePropertyを合成することも可能
// (Output3は、Input1 == Input2なら"同じ", それ以外なら"違う"となる)
Output3 = Input1.CombineLatest(Input2, (x, y) => (x == y ? "同じ" : "違う")).ToReactiveProperty();

 そして上記のように連動している場合、「連動先は連動元で決まるから、連動先は読み取り専用でも構わない」ということがよくあります。そういった際に用いるのがReadOnlyReactivePropertyです。
 ……ReadOnlyReactivePropertyの使い方は、ReactivePropertyだった箇所を単純に置き換えるだけですので省略します(上記で言えばOutput1Output3に用いる)。

 なお、CombineLatestメソッドは、3つ以上のReactivePropertyを合成するのにも使えます。

using System.Reactive.Linq;
public ReactiveProperty<int> Input1 { get; }
public ReactiveProperty<string> Input2 { get; }
public ReactiveProperty<bool> Input3 { get; }
public ReactiveProperty<decimal> Input4 { get; }
public ReactiveProperty<string> Output1 { get; }
public ReactiveProperty<string> Output2 { get; }

Output1 = Input1.CombineLatest(Input2, Input3,
    (x, y, z) => func1(x, y, z)
).ToReactiveProperty();

Output2 = Input1.CombineLatest(Input2, Input3, Input4,
    (x, y, z, w) => func2(x, y, z, w)
).ToReactiveProperty();

Q. ラムダ式の「() => hoge();」と「_ => hoge();」って何が違うの?

 前者は「引数を受け取らず、hoge()の返り値を返す」、後者は「引数を1つ取り、hoge()の返り値を返す」といった意味です。実は 変数名として「_」1文字だけでも全然アリ なのでその値を使えるのですが、「_」1文字にすることで「引数は取るけど使わないよ」といった意思表示をすることができます。

 実はReactivePropertyの場合、Subcribeメソッド内でラムダ式を使うと、引数で呼び出し元の変数の中身を使用することができます。例えば、ReactiveProperty<Type> Xに対してX.Subcribe(x => hoge(x));といったコードが書けます(この際、ラムダ式内の変数xはType型となる)。もっとも、ReactiveCommand型だとobject型が引数の型にやってきますが……。

using System;
// 値が変更された時の動作
SelectedMode.Subscribe(_ => DoFunc(SelectedMode.Value));
// 実はこういった風に書ける
SelectedMode.Subscribe(x => DoFunc(x));
// 更に、DoFuncメソッドが引数1のみでオーバーロードが無ければここまで略せる(意味は上と等価)
SelectedMode.Subscribe(DoFunc);

 しかし、ReactiveProperty<Type> X購読するイベント内でXの中身を使用しない 場合、X.Subcribe(_ => hoge());といった風に 「_」を引数名として使用することで明示する ことができるのです。

Q. さっきから言ってる「購読」ってどういう意味なの?

「購読」を説明する前に、まず「イベント」について説明しましょう。
 イベントとは、「作業を完了した」「ボタンを押した」など、「別の行動を起こすためのキッカケとなる行動」のことです。
 ここで、「別の行動」を起こすオブジェクト(イベント発生側)と「キッカケとなる行動」を起こすオブジェクト(イベント受取側)は 基本的に別である ことに注意しましょう。
 イベントに関係する処理としては、大きく分けて次の3種類があります。

  • 処理1:イベント発生側に、イベント受取側の情報を登録する
  • 処理2:イベント発生側が、「イベントを起こした」という情報をイベント受取側に送信する
  • 処理3:イベント発生側から、イベント受取側の情報を削除する

observableobserver.png
(画像引用元:++C++; // 未確認飛行 C)

 そして「購読」とは、この処理1のことを指します。つまり、ReactiveProperty<T>Subcribeメソッドでラムダ式を受け取る(=購読する)場合、ReactiveProperty<T>が「(値を変更する)イベント発生側」、ラムダ式が「イベント受取側」となります。
 また、後述するReactiveCommandにもSubcribeメソッドがありますが、こちらもReactiveCommandが「(ボタン操作などの)イベント発生側」となります。

Q. ReactiveCommandはどう記述すればいいの?

 先にICommandについて説明すると、これはボタン操作などの「コマンド」をMVVM用に抽象化したものです。コマンドには「実行できるか」という要素(CanExecuteメソッド)と「実行時に何をするか」という要素(Executeメソッド)があり、それぞれを実装する必要があります。
 一方、ReactiveCommand型を使うと、そうした実装の手間を大幅に省くことができます。こちらはReactiveProperty<T>型と異なり、.Valueを付けなくてもいいことに注意しましょう。

C#側の記述.cs

/* --------------- */
/* |従来の記述方法| */
/* --------------- */
using System;
using System.Windows.Input;
public class CommandBase : ICommand
{
    Action action;
    public bool CanExecute(object parameter) => true;
    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter) { action(); }
    public CommandBase(Action action) { this.action = action; }
}
class ViewModel : INotifyPropertyChanged
{
    public ICommand ButtonCommand{ get; private set; }
    private void ButtonAction(){(中略)}
    public ViewModel(){
        ButtonCommand = new CommandBase(ButtonAction);
    }
}

/* --------------------------------- */
/* |ReactivePropertyにおける記述方法| */
/* --------------------------------- */
using Reactive.Bindings;
using System;
class ViewModel : INotifyPropertyChanged{
    public ReactiveCommand ButtonCommand {get;} = new ReactiveCommand();
    private void ButtonAction(){(中略)}
    public MainViewModel(){
        ButtonCommand.Subscribe(_ => ButtonAction());
    }
}

XAML側の記述.xml

<Button Command = {Binding ButtonCommand}>

Q. ReactiveCommandもSubscribeでイベントを購読するんだよね?

 その通りです。ReactiveProperty<T>では「値が変更された時に発動」していましたが、ReactiveCommandでは「コマンドが実行された時に発動」します。

Q. 「次の条件を満たした場合にコマンドを有効にする」処理って書くの面倒なんだよねー

 そういった時にこそReactivePropertyです。ReactivePropertyにはToReactiveCommandという拡張メソッドがあり、これを使うとIObservable<bool>からReactiveCommand型を生成できます。これから、
IObservable<bool>がtrueになった(=条件を満たした)ときにだけReactiveCommand先のオブジェクトが有効になる」
といった処理を簡単に記述できるようになります。
 また、複数の条件を組み合わせることで、「全ての条件を満たした時」や「どれか1つの条件を満たした時」といった有効条件も記述可能です。

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

// 何らかのコマンド
public ReactiveCommand ButtonCommand {get; private set; }
// 何らかのフラグ
public ReactiveProperty<bool> ButtonFlg1 { get; } = new ReactiveProperty<bool>();
public ReactiveProperty<bool> ButtonFlg2 { get; } = new ReactiveProperty<bool>();
public ReactiveProperty<bool> ButtonFlg3 { get; } = new ReactiveProperty<bool>();

// フラグが有効な時にのみコマンドを有効にする(例えばボタンのコマンドが無効になっている際は、ボタン自体も無効色になる)
ButtonCommand = ButtonFlg1.ToReactiveCommand();
// フラグが2つとも有効な場合にのみコマンドを有効にする
ButtonCommand = ButtonFlg1.CombineLatest(ButtonFlg2, (x,y) => x & y).ToReactiveCommand();
// フラグが3つとも有効な場合にのみコマンドを有効にする
ButtonCommand = new[] { ButtonFlg1, ButtonFlg2, ButtonFlg3 }
    .CombineLatest(x => x.All(y => y)).ToReactiveCommand();

// フラグが1つでも有効な場合にのみコマンドを有効にする
ButtonCommand = ButtonFlg1.CombineLatest(ButtonFlg2, (x,y) => x | y).ToReactiveCommand();
// フラグが1つでも有効な場合にのみコマンドを有効にする
ButtonCommand = new[] { ButtonFlg1, ButtonFlg2, ButtonFlg3 }
    .CombineLatest(x => x.Any(y => y)).ToReactiveCommand();

// フラグが全て有効な場合にのみコマンドを有効にする
ButtonCommand = new[] { ButtonFlg1, ButtonFlg2, ButtonFlg3 }
    .CombineLatestValuesAreAllTrue().ToReactiveCommand();
// フラグが全て無効な場合にのみコマンドを有効にする
ButtonCommand = new[] { ButtonFlg1, ButtonFlg2, ButtonFlg3 }
    .CombineLatestValuesAreAllFalse().ToReactiveCommand();

Q. 入力値チェックってどう書けばいいの?

  • ValidationAttributeを継承したクラスをReactiveProperty<T>にあてがう。「IsValidメソッドがtrueを返す=入力値が正常」ということなので注意
  • 以下のケースでは「入力した文字列がint型にパースできるか」をValidation内容としているが、これは入力が文字列だからこそできるのであって、例えばReactiveProperty<int>に対してはその手が使えない(パース可否でValidationできない)ことに注意
// バリデーション用のクラス
// (以下のサンプルは、int型にパースできた時のみ「入力値が正常」だと判断する)
using System.ComponentModel.DataAnnotations;
public class IntValidationAttribute : ValidationAttribute {
    public override bool IsValid(object value)
        => int

 
  • 2018.08.08 Wednesday
  • 16:55

-

Viewのコードビハインドから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; } } ViewのコードビハインドからViewModelの型や実装をを気にせずメソッド&プロパティにアクセスする

ViewのコードビハインドからViewModelの型や実装をを気にせずメソッド&プロパティにアクセスする

例えば……

WPFで画面にドラッグ&ドロップでファイルを投げた時の処理を例として。
ViewのXAMLで

View.xaml

<Window x:Class="AmazonToYamatoB2Converter.Views.View"
        AllowDrop="True"
        Drop="View_Drop">

    <Window.DataContext>
        <vm:ViewModel/>
    </Window.DataContext>

    <Grid>
        <!-- 画面 -->
    </Grid>
</Window>

ってして、コードビハインドでドラッグ&ドロップを処理しようとするんだけど

View.xaml.cs

    public partial class View : Window
    {
        public View()
        {
            InitializeComponent();
        }

        private void View_Drop(object sender, DragEventArgs e)
        {
            var dropFileList = (e.Data.GetData(DataFormats.FileDrop) as string[]).ToList();
            // ここから先はViewModelの処理としたい
        }

    }

ドラッグ&ドロップで渡されたファイル(パス)の処理はViewModelの責務じゃないかなと思うんですよ。
Modelに渡したりとかするわけですし。
ViewModelクラスにはその為の処理としてpublic void AddDragAndDropsFiles(List<string> fileList)ってメソッドが実装されているとします。
で、そのメソッド呼ぶ為にViewからViewModelを参照するわけです。

普通にViewクラスからViewModelクラスを参照する

View.xaml.cs

        private ViewModel VM
        {
            get { return this.DataContext as ViewModel; }
        }

        private void View_Drop(object sender, DragEventArgs e)
        {
            var dropFileList = (e.Data.GetData(DataFormats.FileDrop) as string[]).ToList();
            // ここから先はViewModelの処理としたい
            this.VM. AddDragAndDropsFiles(dropFileList);
        }

まぁよくあるやつです。
これの面倒な点は、ViewModelって型を書かないと行けないって所です。
名前空間usingして、ViewModelの型を補完使って入力。(逆もある)
ViewModelの型を変更しくなったらコードビハインドも変更しないといけない。

ならInterfaceは?

書くの面倒だからコード例は出さない。
確かにそれでいいかもしれない。
でも、それも結局Interfaceの型を変更する場合同じ事なんですよね。
usingするのも変わらんし。
Iなんたらかんたらってのを沢山継承するViewModelになることないですか?

Behavior作れば?

書くの(ry
この辺りを参照して下さい。
確かにコードビハインドにコード書く必要もなくなるのいいかもね。
Interface書くのすら面倒臭がる俺にこんな長いコードを書けとか鬼か。
再利用性はあるかもしれない。
が、でもちょっと使いづらとかで機能追加してコードが読みづらくなるの、よくある話ですよね。
そもそもドラッグ&ドロップでのファイルの受け渡し程度にこんな量のコード書くの?

これでいいんじゃね?

C# 4.0からだけど、dynamicってキーワードがある。
これを使うと

View.xaml.cs

        private dynamic VM
        {
            get { return this.DataContext; }
        }

        private void View_Drop(object sender, DragEventArgs e)
        {
            var dropFileList = (e.Data.GetData(DataFormats.FileDrop) as string[]).ToList();
            // ここから先はViewModelの処理としたい
            this.VM.AddDragAndDropsFiles(dropFileList);
        }

こうなる。
何が良いって、VMはViewModelクラスじゃなくてもpublic void AddDragAndDropsFiles(List<string> fileList)メソッドを実装しているクラスならOKって所。
ViewがViewModelの型や実装を気にしなくて良い。
ViewModelの事を気にせずViewだけの実装を書いておいて、後ViewModel実装するって事も出来る。
なお、呼び出してるメソッドが実装されてなかったらコンパイルエラーは発生せず、実行時にRuntimeBinderExceptionという例外が発生します。

「C#という言語で、メソッド実装してなかったらコンパイルエラーじゃなくて実行時エラーになるのってどうなの?」
という指摘は最もだと思います。潔癖症には辛いかもしれない。
でも、InterfaceやBehaviorの作成よりは読みやすいし、書くコード量も少なくて済むので悪くないんじゃ無いかと思います。



iPadから送信
  • 2018.08.08 Wednesday
  • 16:55