◇Pile Up◇ --Keenag Blog--

プログラミング備忘録ブログです。C#、WPFの記事が中心となります。

【WPF】選択・コピー不可で見た目も変わらないコントロールを作りたい

WPFでコントロールを読み取り専用にしたいとき、コントロールのIsReadOnlyを"True"にし、実現するときが多いかと思います。
今回は、

  • 読み取り専用にしたいけど、選択・コピーはされたくない。
  • IsEnabledプロパティをFalseにして、グレーアウトはさせたくない。(見た目は変えたくない)

というときに使用できる便利なプロパティ、IsHitTestVisibleの説明となります。
上記を満たしたいとき、以下の1行をコントロールに追加するだけで、見た目は変わらず、選択もコピーもできないコントロールを作成することができます。

 <TextBox  IsHitTestVisible="False"  …

見た目のイメージはこのようになります。 f:id:Keenag:20171221150257p:plain 今回はTextBoxでサンプルを作りましたが、ボタン、プルダウン、チェックボックス等にも、もちろん使用可能です。 今回はこれで終わりです。それではまた。

【デザインパターン】ChainOfResponsibilityパターン

はじめに

本エントリーは某社内で実施したデザインパターン勉強会向けの資料となります。
書籍「Java言語で学ぶデザインパターン入門」をベースに学習を進めていますが、 サンプルコードはC#に置き換えて解説します。

ChainOfResponsibilityパターンとは

複数のオブジェクトをチェーン(鎖)のようにつないでおき、要求を処理することが可能なオブジェクトにわたるまで、要求を受け流していくパターンです。


サンプルプログラムのクラス図

ChainOfResponsibilityパターンを使用したサンプルプログラムを紹介します。
ChainOfResponsibility_class.JPG


各クラスの役割

クラス名 役割
Issue 発生した問題を表すクラス。問題管理番号(IssueNumber)を持つ
Support 問題解決のサポートを行う抽象クラス
LimitSupport 問題解決のサポートを行う具象クラス(指定した番号未満の問題を解決)
OddSupport 問題解決のサポートを行う具象クラス(奇数番号の問題を解決)
SpecialSupport 問題解決のサポートを行う具象クラス(特定の問題を解決)
Program Supportたちの連鎖を作り、問題を発生させる動作テスト用クラス

Issueクラス

Issueクラスは、発生した問題を表現するクラスです。IssueNumberは問題を管理する番号です。
コンストラクタで、IssueNumberを設定します。

namespace ChainOfResponsibilitySample
{
    public class Issue
    {
        public int IssueNumber { get; private set; }

        public Issue(int issueNumber)
        {
            IssueNumber = issueNumber;
        }
    }
}

Supportクラス

Supportクラスは、問題を解決する連鎖を作るための抽象クラスです。
_nextSupportフィールドは、次の問題解決先を差します。
SetNextSupportメソッドは、次の問題解決先を設定します。
Resolveメソッドは、サブクラスで実装することを想定した抽象メソッドです。戻り値がtrueのときは要求が処理されたことを表し、falseのときは要求はまだ処理されていない(次のサポート先に問題解決を任せる)ことを表します。
SupportIssueメソッドは、Resolveメソッドを呼び出し、戻り値がfalseなら、次のサポートに問題解決を任せます。 次のサポートがいない場合は、連鎖の最後となり、サポートできなかった旨を通知するメッセージを出力します。

using System;

namespace ChainOfResponsibilitySample
{
    public abstract class Support
    {
        private string _staffName;

        private Support _nextSupport;

        public Support(string staffName)
        {
            _staffName = staffName;
        }

        public Support SetNextSupport(Support nextSupport)
        {
            return _nextSupport = nextSupport;
        }

        public void SupportIssue(Issue issue)
        {
            if (Resolve(issue))
            {
                Console.WriteLine("問題" + issue.IssueNumber + "は" + _staffName + "が解決しました。");
            }
            else if (_nextSupport != null)
            {
                _nextSupport.SupportIssue(issue);
            }
            else
            {
                Console.WriteLine("問題" + issue.IssueNumber + "は現在のサポート体制では解決できませんでした。");
            }
        }

        protected abstract bool Resolve(Issue issue);

    }
}

LimitSupportクラス

LimitSupportクラスは、limitIssueNumberで指定した問題管理番号未満の問題を解決するクラスです。

namespace ChainOfResponsibilitySample
{
    public class LimitSupport : Support
    {

        private int _limitIssueNumber;

        public LimitSupport(string staffName, int limitIssueNumber) : base(staffName)
        {

            _limitIssueNumber = limitIssueNumber;
        }

        protected override bool Resolve(Issue issue)
        {
            return issue.IssueNumber < _limitIssueNumber;
        }
    }
}

OddSupportクラス

OddSupportクラスは、問題管理番号が奇数の問題を解決するクラスです。

namespace ChainOfResponsibilitySample
{
    public class OddSupport : Support
    {
        public OddSupport(string staffName) : base(staffName)
        {
        }

        protected override bool Resolve(Issue issue)
        {
            return issue.IssueNumber % 2 == 1;
        }
    }
}

SpecialSupportクラス

SpecialSupportクラスは、指定した問題管理番号のみ問題を解決するクラスです。

namespace ChainOfResponsibilitySample
{
    public class SpecialSupport : Support
    {
        private int _specialNumber;

        public SpecialSupport(string staffName, int specialNumber) : base(staffName)
        {
            _specialNumber = specialNumber;
        }

        protected override bool Resolve(Issue issue)
        {
            return issue.IssueNumber == _specialNumber;
        }
    }
}

Programクラス

動作確認クラスでは、まず各サポートのインスタンスを作成しています。次にSetNextSupportメソッドを使用して、サポートの順番を設定しています。
そして、6件の問題を発生させ、LimitSupportであるNagatomoさんに1次処理をお願いしています。

using System;

namespace ChainOfResponsibilitySample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Support limitSupport = new LimitSupport("Nagatomo", 3);
            Support oddSupport = new OddSupport("Kagawa");
            Support SpecialSupport = new SpecialSupport("Honda", 4);

            limitSupport.SetNextSupport(oddSupport).SetNextSupport(SpecialSupport);

            var IssueCount = 6;
            // 6件の問題が発生
            for (int i = 1; i <= IssueCount; i++)
            {
                limitSupport.SupportIssue(new Issue(i));
            }

            Console.ReadKey();
        }
    }
}

実行結果

実行結果は以下のようになります。

問題1はNagatomoが解決しました。
問題2はNagatomoが解決しました。
問題3はKagawaが解決しました。
問題4はHondaが解決しました。
問題5はKagawaが解決しました。
問題6は現在のサポート体制では解決できませんでした。

問題1,2は3未満のため、LimitSupportで、Nagatomoが解決できます。
問題3はNagatomoでは解決できないため、解決処理の責任がKagawaに移ります。OddSupportのKagawaは、問題3は解決できますが偶数である問題4は解決できません。
したがって、問題4の解決処理の責任はHondaに移ります。SpecialSupportのHondaは問題4のみ解決することができます。
問題5は3以上且つ奇数のため、Nagatomo→Kagawaと責任が受け流され、Kagawaが解決します。
問題6は、全てのサポートで解決が不可能であるため、サポートできない旨のメッセージが表示されています。


ChainOfResponsibilityパターン使用によるメリット

  • 利用者側は、内部構造を意識することなく、連鎖関係のある任意のオブジェクトに要求をすることで、適切な処理者によって要求が処理される
  • 処理者側は、自分ができる処理のみ対応すれば良いので、簡潔に処理が記述できる

ChainOfResponsibilityパターンを使わないほうが良い場合

 利用者と処理者の関係が固定的で、処理速度が重要な場合は使用しないほうが良い


振り返り

ChainOfResponsibilityパターンとは 複数のオブジェクトをチェーン(鎖)のようにつないでおき、要求を処理することが可能なオブジェクトにわたるまで、要求を受け流していくパターンです。


サンプルコード

以下に公開しています。
https://github.com/Keenag/DesignPatternStudyGroup/tree/master/ChainOfResponsibilitySample


【XAML】RadioButtonは選択されているボタンだけタブ遷移したい

今回は、XAMLのRadioButtonのタブ遷移の話です。
デフォルトの設定では、グルーピングされているRadioButtonは、選択されている、されていないに関わらず、すべてのRadioButtonにタブフォーカスが当たる仕様となっています。
「選択されているラジオボタンだけタブ遷移させたい」
そんなときありますよね?あります。

そんなときは、RadioButtonの親要素のStackPanelコントロールに以下のプロパティを追加してやればいいんです。

KeyboardNavigation.TabNavigation="Once" 

実際のコードでは次のように使います。

 <Grid>
        <Grid Height="60" VerticalAlignment="Top">
            <StackPanel  Orientation="Horizontal"
                         HorizontalAlignment="Center"
                         KeyboardNavigation.TabNavigation="Once"
                         KeyboardNavigation.TabIndex="0" >
                <RadioButton Content="Button1"
                                 VerticalContentAlignment="Center"
                                 GroupName="RadioGroup"
                                 />
                <RadioButton Content="Button2"
                                 VerticalContentAlignment="Center"
                                 Margin="30,0,0,0"
                                 GroupName="RadioGroup"
                                 />
            </StackPanel >
        </Grid>
        <Button Content="Button" Height="30" Width="75"  TabIndex="1" />
    </Grid>

こうすることで、StackPanelの中の要素には、1度しかタブフォーカスが当たらないように設定できます。 今回はRadioButtonの例で紹介しましたが、ほかのコントロールでも同様に制御が可能です。 以下、MSの公式サイトの情報です。
KeyboardNavigationMode 列挙型
今回はこれで終わりです。それではまた。

【WPF】ポップアップで入力された値を親画面で受け取る方法

今回は、WPFで親画面でポップアップを出した際に、入力された値を親画面で受け取る方法に ついてサンプルアプリケーションをつくったので、説明してきたいと思います。

各クラスの役割

各クラスの役割はこんな感じです。 なお、いくつかのクラスは省略しています。詳細はサンプルコードを参考にしてください。

クラス名 役割
MainWindow.xaml ポップアップ表示元の親画面のXAML
ChildDialog.xaml 親画面から表示されるポップアップのXAML
InputValueNotification.cs 子から親へ値の橋渡しをするNotificationクラス
MainWindowViewModel.cs ポップアップ表示元の親画面のViewModelクラス
ChildDialogViewModel.cs 親画面から表示されるポップアップのViewModelクラス

MainWindow.xaml

親画面では、ポップアップで入力された文字を表示するTextBoxが用意されています。
また、ポップアップを表示するための「ポップアップ表示」ボタンを配置されています。このボタンを押下することで、prismを使用し、PopupWindowActionでポップアップが表示されます。

<Window x:Class="NotificationSample.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:views="clr-namespace:NotificationSample.Views"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="NotificationSample" Height="350" Width="525"
        Background="LightSlateGray">
    <i:Interaction.Triggers>
        <prism:InteractionRequestTrigger SourceObject="{Binding OpenChildDialogRequest}">
            <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" >
                <prism:PopupWindowAction.WindowContent>
                    <views:ChildDialog />
                </prism:PopupWindowAction.WindowContent>
            </prism:PopupWindowAction>
        </prism:InteractionRequestTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="180"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0"
               Grid.Column="0"
               Content="ポップアップで入力された文字:"
               FontSize="12"
               HorizontalAlignment="Right"
               VerticalAlignment="Bottom"/>
        <TextBox Grid.Row="0"
                 Grid.Column="1"
                 Height="30"
                 HorizontalAlignment="Left"
                 VerticalAlignment="Bottom"
                 Width="200"
                 Text="{Binding OwnerInputValue.Value}"/>
        <Button Grid.Row="1"
                Grid.Column="1"
                Content="ポップアップ表示"
                Width="150"
                Height="50"
                HorizontalAlignment="Left"
                VerticalAlignment="Center"
                Command="{Binding OpenChildDialogCommand}"/>
    </Grid>
</Window>

ChildDialog.xaml

ポップアップ画面では、親画面に引き継ぎたい文字を入力するTextBoxと、ポップアップを閉じ、親画面へ戻る「親画面へ」ボタンが配置されています。

<UserControl x:Class="NotificationSample.Views.ChildDialog"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True"
             Width="525"
             Height="350"
             Background="LightSlateGray">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="180"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0"
               Grid.Column="0"
               Content="親画面に引き継ぐ文字:"
               FontSize="12"
               HorizontalAlignment="Right"
               VerticalAlignment="Bottom"/>
        <TextBox Grid.Row="0"
                 Grid.Column="1"
                 Height="30"
                 HorizontalAlignment="Left"
                 VerticalAlignment="Bottom"
                 Width="200"
                 Text="{Binding ChildInputValue.Value}"/>
        <Button Grid.Row="1"
                Grid.Column="1"
                Content="親画面へ"
                Width="150"
                Height="50"
                HorizontalAlignment="Left"
                VerticalAlignment="Center"
                Command="{Binding NavigateOwnerCommand}"/>
    </Grid>
</UserControl>

InputValueNotification.cs

prismのNotificationクラスを継承したInputValueNotificationクラスには、プロパティにInputValueを定義します。このプロパティにポップアップ内のTextBoxで入力された値を詰め、親画面のViewModelで値を受け取ります。

using Prism.Interactivity.InteractionRequest;

namespace NotificationSample.ViewModels
{
    public class InputValueNotification : Notification
    {
        public string InputValue { get; set; }
    }
}

MainWindowViewModel.cs

OwnerInputValueは、ポップアップで入力された値を表示するために使用するプロパティです。
OpenChildDialogCommandは、親画面で「ポップアップ表示」ボタンを押下した際に呼ばれるCommandです。
OpenChildDialogRequestは、ポップアップの表示をViewに通知するときに使用されます。
OpenChildDialogCommandが実行されると、inputValueNotificationを引数にOpenChildDialogRequestが呼ばれ、 ポップアップが表示されます。
引数で渡したinputValueNotificationが保持するInputValueプロパティがポップアップ側で変更されていたら、OwnerInputValueに、InputValueプロパティの値をセットします。

using Prism.Commands;
using Prism.Interactivity.InteractionRequest;
using Prism.Mvvm;
using Reactive.Bindings;

namespace NotificationSample.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {   
        public ReactiveProperty<string> OwnerInputValue { get; } = new ReactiveProperty<string>();

        public DelegateCommand OpenChildDialogCommand { get; }

        public InteractionRequest<InputValueNotification> OpenChildDialogRequest { get; } = new InteractionRequest<InputValueNotification>();
        
        public MainWindowViewModel()
        {
            OpenChildDialogCommand = new DelegateCommand(() =>
            {
                var inputValueNotification = new InputValueNotification() { Title = "NotificationSample" };
                OpenChildDialogRequest.Raise(inputValueNotification);
                if (inputValueNotification.InputValue != null)
                {
                    OwnerInputValue.Value = inputValueNotification.InputValue;
                }
            });

        }
    }
}

ChildDialogViewModel.cs

ChildInputValueはポップアップのTextBoxのTextにBindingされているプロパティです。 ポップアップで「親画面へ」ボタンが押下された際はNavigateOwnerCommandが実行されます。
NavigateOwnerCommandが実行された際は、ChildInputValueの値をInputValueNotificationのInputValueプロパティに詰め、FinishInteractionでポップアップを閉じます。

using Prism.Commands;
using Prism.Interactivity.InteractionRequest;
using Prism.Mvvm;
using Reactive.Bindings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NotificationSample.ViewModels
{
    public class ChildDialogViewModel : BindableBase, IInteractionRequestAware
    {
        public INotification Notification { get; set; }
        public Action FinishInteraction { get; set; }
        public ReactiveProperty<string> ChildInputValue { get; } = new ReactiveProperty<string>();


        public DelegateCommand NavigateOwnerCommand => new DelegateCommand(() =>
        {
            ((InputValueNotification)Notification).InputValue = ChildInputValue.Value;
            FinishInteraction();
        });
    }
}

実行結果

実行結果は次のようになります。
ポップアップのTextBoxで入力された値を親画面で受け取れていることが確認できます。

サンプルコード

以下に公開しています。
https://github.com/Keenag/SampleCode/tree/master/NotificationSample

【WPF】タブ順指定ではまりかけた話

XAMLではTabIndexを指定することで、ユーザのタブ操作によるフォーカスの順番を設定することができますが、 Windowの中にUserControlを表示するような画面の場合、少しはまりかけたので、備忘録として残します。 以下サンプルコードで説明していきます。
なお、今回も一部のソースコードの説明は割愛しています。詳細はサンプルコードを参考にしてください。

MainWindow.xaml

MainWindow.xamlクラスは、WindowのLoadedイベントで、MainwindowViewModel.csのNavigateCommandが呼ばれ、ContentControl部分にMainWindowViewModel.csで指定したUserControlが組み込まれるという作りになっています。

<Window x:Class="TabIndexSample.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="TabIndexSample" Height="350" Width="525">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding NavigateCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <ContentControl prism:RegionManager.RegionName="ContentRegion"
                        FocusVisualStyle="{StaticResource FocusVisualStyleKey}"/>
    </Grid>
</Window>

HogeUserControl.xaml

HogeUserControl.xamlは、MainWindow.xamlのContentControlに表示されるUserControlで、 TabIndexでユーザのタブ操作を制御しています。

<UserControl x:Class="TabIndexSample.Views.HogeUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True"
             Height="350"
             Width="525"
             Background="LightSlateGray">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="100"/>
            <RowDefinition Height="100"/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0"
               Content="TabIndex1"
               HorizontalAlignment="Left"
               VerticalAlignment="Center"
               Margin="90,0,0,0"/>
        <TextBox Grid.Row="0"
                 Width="200"
                 Height="30"
                 TabIndex="1"
                 FocusVisualStyle="{StaticResource FocusVisualStyleKey}"/>
        <Label Grid.Row="1"
               Content="TabIndex2"
               HorizontalAlignment="Left"
               VerticalAlignment="Center"
               Margin="90,0,0,0"/>
        <TextBox Grid.Row="1"
                 Width="200"
                 Height="30"
                 TabIndex="2"
                 FocusVisualStyle="{StaticResource FocusVisualStyleKey}"/>
        <Label Grid.Row="2"
               Content="TabIndex0"
               HorizontalAlignment="Left"
               VerticalAlignment="Center"
               Margin="90,0,0,0"/>
        <TextBox Grid.Row="2"
                 Width="200"
                 Height="30"
                 TabIndex="0"
                 FocusVisualStyle="{StaticResource FocusVisualStyleKey}"/>
    </Grid>
</UserControl>

実行結果(修正前)

このソースコードで実行すると、実行結果はこのようになります。

HogeUserControl.xaml内のTab操作を終えると、タブのフォーカスが画面全体にあたってしまいます。
これでは、意図していたタブ操作ではありません。 そこで、HogeUserControlのUserControlタグに

IsTabStop="False"

を追加してみたのですが、結果は変わりません。
UserContorolの表示元であるMainWindow.xamlのContentControlにIsTabStop="False"を追加することで解決することができました。

        <ContentControl prism:RegionManager.RegionName="ContentRegion"
                        FocusVisualStyle="{StaticResource FocusVisualStyleKey}"
                        IsTabStop="False"/>

実行結果(修正後)

修正後の実行結果はこのようになります。
無事、画面全体へのフォーカスが外れました。

サンプルコード

以下に公開しています。
https://github.com/Keenag/SampleCode/tree/master/TabIndexSample

【WPF】ReactivePropertyを使用してボタンの活性非活性を制御する方法

ReactivePropertyはMVVM+リアクティブプログラミングを快適にサポートしてくれるライブラリです。
今回は、ReactivePropertyを使用して、ボタンの制御サンプルを作ってみたので、備忘録として残したいと思います。

なお、ReactivePropertyについてもっと知りたい方は、MVVMをリアクティブプログラミングで快適にReactivePropertyオーバービューをご覧になってみてください。

では、サンプルプログラムを見ていきましょう。

MainWindow.xaml

MainWindow.xamlクラスには、ボタンの活性、非活性を制御するグルーピングされた「活性」ラジオボタン、「非活性」ラジオボタンを配置します。 「活性」ラジオボタンのIsCheckedプロパティには、IsActiveをバインディングしています。 ボタンには、CommandプロパティにHogeCommandをバインディングしています。

<Window x:Class="ReactiveCommandSample.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="ReactiveCommandSample"
        Height="350"
        Width="525"
        Background="LightSlateGray">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="80"/>
            <RowDefinition Height="40"/>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="200"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <RadioButton Grid.Row="0"
                     Grid.Column="1"
                     Content="活性"
                     VerticalAlignment="Bottom"
                     HorizontalAlignment="Left"
                     TabIndex="0"
                     HorizontalContentAlignment="Left"
                     VerticalContentAlignment="Center"
                     Padding="1"
                     GroupName="HogeRadio"
                     IsChecked="{Binding IsActive.Value}"
                     Height="27"
                     Width="126"
                     FocusVisualStyle="{x:Null}"
                     />
        <RadioButton Grid.Row="1"
                     Grid.Column="1"
                     Content="非活性"
                     VerticalAlignment="Bottom"
                     HorizontalAlignment="Left"
                     TabIndex="1"
                     VerticalContentAlignment="Center"
                     GroupName="HogeRadio"
                     Height="27"
                     Width="139"
                     FocusVisualStyle="{x:Null}"
                     />
        <Button Grid.Row="2"
                Grid.Column="1"
                Content="Button"
                HorizontalAlignment="Left"
                VerticalAlignment="Bottom"
                Width="99"
                Height="34"
                Command="{Binding HogeCommand}"/>
    </Grid>
</Window>

MainWindowViewModel.cs

MainWindowViewModelクラスでは、「活性」ボタンの変更通知を受け取るReactiveProperttyのIsActiveを定義します。また、ReactiveCommandのHogeCommandを定義します。
HogeCommandは、IsActiveがTrueの時のみ(「活性」ボタンが選択されている時のみ)活性化し、押下された際はOnClickメソッドが呼ばれます。

using Prism.Mvvm;
using Reactive.Bindings;
using System.Reactive.Linq;

namespace ReactiveCommandSample.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        public ReactiveProperty<bool> IsActive { get; } = new ReactiveProperty<bool>(true);
        public ReactiveCommand HogeCommand { get; }

        public MainWindowViewModel()
        {
            HogeCommand = IsActive.Select(x => x == true).ToReactiveCommand();
            HogeCommand.Subscribe(OnClick);
        }

        public void OnClick()
        {
            //ボタンが押下された際の処理
        }
    }
}

実行結果

実行結果はこのようになります。
「活性」ラジオボタンが押下されているときは、ボタンが活性化しています。 f:id:Keenag:20171008225521p:plain

「非活性」ラジオボタンが押下されているときは、ボタンが非活性化しています。 f:id:Keenag:20171008225611p:plain

サンプルコード

以下に公開しています。
https://github.com/Keenag/SampleCode/tree/master/ReactiveCommandSample

【WPF】1画面に複数のUserControlを可変で表示する方法

WPFで1つの画面の中に複数のUseControlを可変で表示させたいという要件があり、
以下のように実装することで実現することができました。

各クラスの役割

各クラスの役割はこんな感じです。 なお、いくつかのクラスは省略しています。詳細はサンプルコードを参考にしてください。

クラス名 役割
DisplayControlViewModel.cs 画面に表示させるUserControlのViewModelを抽象化したクラス
ComboBoxControlViewModel.cs DisplayControlViewmodelを継承したComboBoxControl.xaml のViewModel
ComboBoxControl.xaml ComboBoxを持ったUserControlクラス
MainWindowViewModel.cs MainWindow.xamlのViewModel
MainWindow.xaml 複数のUserControlを表示するWindow

DisplayControlViewModel.cs

画面に表示させるUserControlに紐づくViewModelクラスを抽象化したクラスです。

namespace DisplayUserControlListSample.ViewModels
{
    public abstract class DisplayControlViewModel
    {
    }
}

ComboBoxControlViewModel.cs

ComboBoxControl.xamlのViewModelクラスです。DisplayControlViewModelを継承しています。
コンボボックスに表示されるItemのコレクションを持ち、コンストラクタで値を初期化しています。

using Reactive.Bindings;

namespace DisplayUserControlListSample.ViewModels
{
    public class ComboBoxControlViewModel : DisplayControlViewModel
    {
        /// <summary>
        /// コンボボックスに表示されるItemのコレクション
        /// </summary>
        public ReactiveCollection<string> Items { get; }

        public ComboBoxControlViewModel()
        {
            Items = new ReactiveCollection<string>() { "Apple", "Banana", "Peach" };
        }
    }
}

ComboBoxControl.xaml

1つのComboBoxのみをもつXAMLです。ItemsSourceにComboBoxControlViewModelからItemsをバインドしています。

<UserControl x:Class="DisplayUserControlListSample.Views.ComboBoxControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True"
             Width="300"
             Height="30">
    <Grid Margin="0,0,0,5" >
        <StackPanel Orientation="Horizontal">
            <ComboBox Height="30"
                      Width="300"
                      FontFamily="MS Gothic"
                      FontSize="12"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      ItemsSource="{Binding Items}"
                       >
            </ComboBox>
        </StackPanel>
    </Grid>
</UserControl>

MainWindowViewModel.cs

DisplayControlViewModelクラスのコレクションをプロパティとしてもちます。このプロパティに表示したいUserControlに紐づいたViewModelのインスタンスを追加します。
※なお、本エントリーではTextBoxControlに関する説明は割愛しています

using Prism.Mvvm;
using Reactive.Bindings;

namespace DisplayUserControlListSample.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        public ReactiveCollection<DisplayControlViewModel> DisplayControlViewModels { get; } = new ReactiveCollection<DisplayControlViewModel>();

        public MainWindowViewModel()
        {
            DisplayControlViewModels.Clear();
            DisplayControlViewModels.Add(new ComboBoxControlViewModel());
            DisplayControlViewModels.Add(new TextBoxControlViewModel());
            DisplayControlViewModels.Add(new ComboBoxControlViewModel());
        }
    }
}

MainWindow.xaml

ItemsControl要素のItemsSourceプロパティにDisplayControlViewModelのリストをバインドします。
WindowのリソースにDataTemplateで表示するUserControlを定義します。
ItemsSourceにバインドされたDisplayControlViewModelのTypeを見て表示するUserControlを切り替えます。

<Window x:Class="DisplayUserControlListSample.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:views="clr-namespace:DisplayUserControlListSample.Views"
        xmlns:viewModels="clr-namespace:DisplayUserControlListSample.ViewModels"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Height="350" Width="525"
        Title="DisplayUserControlListSample"
        Background="LightSlateGray">
    <Window.Resources>
        <DataTemplate DataType="{x:Type viewModels:TextBoxControlViewModel}">
            <views:TextBoxControl/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type viewModels:ComboBoxControlViewModel}">
            <views:ComboBoxControl/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ScrollViewer  Margin="50,50,50,50"
                       HorizontalScrollBarVisibility="Auto"
                       VerticalScrollBarVisibility="Auto"
                       IsTabStop="False">
            <ItemsControl ItemsSource="{Binding DisplayControlViewModels}"
                          VerticalAlignment="Top"
                          IsTabStop="False" />
        </ScrollViewer>
    </Grid>
</Window>

実行結果

実行結果はこのようになります。
MainWindowViewModel.csで追加したViewmodelに紐づくUserControlが表示されます。
f:id:Keenag:20171008174037p:plain

サンプルコード

以下に公開しています。
https://github.com/Keenag/SampleCode/tree/master/DisplayUserControlListSample