RunningCSharp

MS系開発者による、雑多な記事。記事は所属企業とは関係のない、個人の見解です。

C#+XAML:MVVM Light Toolkitのスニペット導入方法など

スニペットのDLと配置(Visual Studio 2015の場合)

1:http://mvvmlight.codeplex.com/sourcecontrol/latestの「Download」リンクから、圧縮された全ソースコードをダウンロード

2:ファイルを解凍し、\Installer\InstallItems\Snippets\CSharp にスニペットがあるのですべてコピー

3:%UserProfile%\Documents\Visual Studio 2015\Code Snippets\Visual C#\My Code Snippets にすべて貼り付け

・(個人的に)よく使うスニペット紹介

「mvvminpc」

プロパティ変更通知メソッド呼び出しが付いたプロパティのスニペット。 下記のようなプロパティ宣言が展開され、プロパティ名(MyPropertyとなっている所)を書き換えると文字リテラルのプロパティ名も連動して書き換えられます。

/// <summary>
/// The <see cref="MyProperty" /> property's name.
/// </summary>
public const string MyPropertyPropertyName = "MyProperty";

private bool _myProperty = false;

/// <summary>
/// Sets and gets the MyProperty property.
/// Changes to that property's value raise the PropertyChanged event. 
/// </summary>
public bool MyProperty
{
    get
    {
        return _myProperty;
    }

    set
    {
        if (_myProperty == value)
        {
            return;
        }

        _myProperty = value;
        RaisePropertyChanged(MyPropertyPropertyName);
    }
}

「mvvmrelay」

コマンドプロパティ用のスニペットスニペットが用意したラムダ式を埋めるだけで操作記述が完結するのがうれしいです。

private RelayCommand _myCommand;

/// <summary>
/// Gets the MyCommand.
/// </summary>
public RelayCommand MyCommand
{
    get
    {
        return _myCommand
            ?? (_myCommand = new RelayCommand(
            () =>
            {

            }));
    }
}

「mvvmrelaygeneric」

上記コマンドプロパティのジェネリック版。 コマンドパラメータを指定するときはこちらを利用。

private RelayCommand<string> _myCommand;

/// <summary>
/// Gets the MyCommand.
/// </summary>
public RelayCommand<string> MyCommand
{
    get
    {
        return _myCommand
            ?? (_myCommand = new RelayCommand<string>(
            p =>
            {

            }));
    }
}

・おまけ(多用はしていない)

「mvvminpclambda」

文字列ではなく、ラムダ式でプロパティそのものを返却することで呼び出し先で式木からプロパティ名を取得している。 スニペット中には使用されない文字列型の宣言が残ったままです。

/// <summary>
/// The <see cref="MyProperty" /> property's name.
/// </summary>
public const string MyPropertyPropertyName = "MyProperty";

private bool _myProperty = false;

/// <summary>
/// Sets and gets the MyProperty property.
/// Changes to that property's value raise the PropertyChanged event. 
/// </summary>
public bool MyProperty
{
    get
    {
        return _myProperty;
    }

    set
    {
        if (_myProperty == value)
        {
            return;
        }

        _myProperty = value;
        RaisePropertyChanged(() => MyProperty);
    }
}

「mvvminpc」、「mvvminpclambda」ともに.netFramework4.5からはnameof演算子があるため、文字列やラムダ式でプロパティ名を渡さなくてよい(MVVMLignt ToolkitのViewModelBaseにもプロパティ名などの引数を受け取らないRaisePropertyChangedのオーバーライドも用意されている)ため、.netFramework4.5(C#5)向けのもう少しすっきりしたスニペットを自作したほうがよいかな、と記事を書きながら思いました。

C#:継承とメソッドの隠蔽で、privateスコープからのアクセス時のみメソッド実装を変える

基底クラスのpublicなメソッドに対し、派生クラスでnewキーワードを用いてprivateなメソッドとして隠蔽を行なうと、

クラス外から呼び出した場合(publicなスコープでのアクセス時)は基底クラスの実装で動作し、

クラス内から呼び出した場合(privateなスコープでのアクセス時)は派生クラスの実装で動作しました。

class Program
{
   static void Main(string[] args)
   {
       Console.WriteLine(new BaseClass().MyMethod());
       Console.WriteLine(new SuperClass().MyMethod());
       Console.WriteLine(new SuperClass().CallLocalMethod());
       Console.ReadLine();
   }

}
public class BaseClass
{
   //隠ぺい対象のメソッド
   public string MyMethod()
   {
       return "aaa";
   }
}

public class SuperClass : BaseClass
{
   //privateなメソッドで隠ぺい
   private new string MyMethod()
   {
       return "bbb";
   }
   //ローカルスコープでのMyMethodの結果を返すメソッド
   public string CallLocalMethod()
   {
       return MyMethod();
   }
}

結果:

aaa

aaa

bbb

MSDNにも記載があります。(https://msdn.microsoft.com/ja-jp/library/aa691135(v=vs.71).aspx)

WPF:TreeViewのExpanderをStyleの変更をせずに非表示にする

<Window x:Class="WpfApplication.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:WpfApplication"
         mc:Ignorable="d"
         Title="MainWindow" Height="350" Width="525">
     <Grid>
         <TreeView>
             <TreeView.Resources>
                 <Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}">
                     <Setter Property="IsExpanded" Value="True" />
                     <Setter Property="local:Att.IsHideExpander" Value="True" />
                 </Style>
             </TreeView.Resources>
             <TreeViewItem Header="parent">
                 <TreeViewItem Header="child" />
             </TreeViewItem>
             <TreeViewItem Header="parent">
                 <TreeViewItem Header="child" />
             </TreeViewItem>
             <TreeViewItem Header="parent">
                 <TreeViewItem Header="child">
                     <TreeViewItem Header="child"/>
                 </TreeViewItem>
             </TreeViewItem>
         </TreeView>
     </Grid>
 </Window>

f:id:ys-soniclab:20160622085426p:plain

上記のような画面で、ツリービューのエクスパンダーを非表示にしようと考えました。

ネットでは良くItemContainerStyleを変更する方法を見かけるのですが、 今回は他のStyleとの兼ね合いがあり、ItemContainerStyleを変更しない対応が必要でした。 そこで添付プロパティを使ってみたところ、うまくいきましたのでメモしておきます。

追加した添付プロパティ(とりあえずWpfApplication名前空間に作成)

    public class Att
    {
        public static bool GetIsHideExpander(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsHideExpanderProperty);
        }

        public static void SetIsHideExpander(DependencyObject obj, bool value)
        {
            obj.SetValue(IsHideExpanderProperty, value);
        }

        // Using a DependencyProperty as the backing store for IsHideExpander.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsHideExpanderProperty =
            DependencyProperty.RegisterAttached("IsHideExpander", typeof(bool), typeof(Att), new PropertyMetadata(false, IsHideExpander_PropertyChanged));

        private static void IsHideExpander_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TreeViewItem treeViewItem = d as TreeViewItem;
            if (treeViewItem == null) return;
            treeViewItem.Loaded += TreeViewItem_Loaded;
        }

        private static void TreeViewItem_Loaded(object sender, RoutedEventArgs e)
        {
            TreeViewItem treeViewItem = sender as TreeViewItem;
            if (treeViewItem == null) return;

            treeViewItem.Loaded -= TreeViewItem_Loaded;

            ToggleButton tg = FindChild<ToggleButton>(treeViewItem);
            if (tg != null)
            {
                tg.Visibility = Visibility.Hidden;
            }
        }

        public static T FindChild<T>(DependencyObject parent) where T : DependencyObject
        {
            if (parent == null) return null;
            if (parent is T) return parent as T;

            T child = null;

            int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < childrenCount; i++)
            {
                var c = VisualTreeHelper.GetChild(parent, i);
                child = FindChild<T>(c);
                if (child != null) break;
            }

            return child;
        }
    }

xamlはTreeView.Resourcesタグを修正

            <TreeView.Resources>
                 <Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}">
                     <Setter Property="IsExpanded" Value="True" />
                     <Setter Property="local:Att.IsHideExpander" Value="True" />
                 </Style>
             </TreeView.Resources>

上記コードを追加することで、エクスパンダーが非表示となります。

f:id:ys-soniclab:20160622085444p:plain

WPF:TextBoxで連続入力中の入力エラーを抑制

<Window x:Class="WpfApplication.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:WpfApplication"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <Grid>
        <TextBox HorizontalAlignment="Stretch" VerticalAlignment="Center"
                  Text="{Binding TestValue, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" />
    </Grid>
</Window>

上記画面にて、バインド元のViewModelのプロパティで入力内容のエラーチェック(数値の場合、1000未満はエラー)を行うものとします。

    public class MainViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string testValue;
        public string TestValue
        {
            get { return testValue; }
            set
            {
                int parse = default(int);
                //数値の場合1000未満はエラー
                if (int.TryParse(value, out parse) && parse < 1000)
                    throw new Exception();
                testValue = value;
                PropertyChanged(this, new PropertyChangedEventArgs("TestValue"));
            }
        }
    }

このアプリケーションを実行し、数値の「5000」をテキストボックスに入力しようとすると、 一文字目の「5」を入力した段階でエラーとなってしまいます。 UpdateSourceTriggerがPropertyChangedなので当たり前の挙動ですが、LostFocusではなくプロパティ変更の段階でエラーを通知したい、でも入力した瞬間に即エラーを出されるのも微妙、そこでDelayで少し通知を遅らせてみます。

<Grid>
    <TextBox HorizontalAlignment="Stretch" VerticalAlignment="Center"
                Text="{Binding TestValue, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True, Delay=500}" />
</Grid>

上記のように、TextプロパティのBInding文の中でDelayを設定すると、入力直後のエラー表示は避けられます。 単位はミリ秒ですので、例では入力終了後0.5秒後にエラー表示されます。

文字を連続で入力している場合は、最終入力から0.5秒後にエラー表示されます。

C#:式木で四則演算を行うデリゲートを生成

とりあえず式木を使ってみたい、位の気持ちで記事にしてしまいました。

引数と定数「2」とで四則演算を行うメソッドを生成するメソッドです。

enum CalcType
{
    Add,
    Subtract,
    Multiply,
    Divide
}

static Func<int, int> CreateExpression2(CalcType type)
{
    var param = Expression.Parameter(type: typeof(int));
    var consttwo = Expression.Constant(2);
    BinaryExpression calc = null;
    switch (type)
    {
        case CalcType.Add:
            calc = Expression.Add(param, consttwo);
            break;
        case CalcType.Subtract:
            calc = Expression.Subtract(param, consttwo);
            break;
        case CalcType.Multiply:
            calc = Expression.Multiply(param, consttwo);
            break;
        case CalcType.Divide:
            calc = Expression.Divide(param, consttwo);
            break;
    }

    if (calc == null) return null;

    var lambda = Expression.Lambda(calc, param);
    return (Func<int, int>)lambda.Compile();
}

以下のように使用します。

static void Main()
{
    var Add = CreateExpression2(CalcType.Add);
    var Subtract = CreateExpression2(CalcType.Subtract);
    var Multiply = CreateExpression2(CalcType.Multiply);
    var Divide = CreateExpression2(CalcType.Divide);
    Console.WriteLine("Add : " + Add(8).ToString());//引数と「2」の足し算結果
    Console.WriteLine("Subtract : " + Subtract(8).ToString());//引数と「2」の引き算結果
    Console.WriteLine("Multiply : " + Multiply(8).ToString());//引数と「2」の掛け算結果
    Console.WriteLine("Divide : " + Divide(8).ToString());//引数と「2」の割り算結果
    Console.ReadLine();
}

結果

Add : 10

Subtract : 6

Multiply : 16

Divide : 4

このくらいなら正直、普通に4つラムダ式を用意したほうが良い気がしてしまいます。 もっと複雑な式を動的生成する場合には、全パターンのラムダ式を用意するより、動的に生成したほうがスマートになるケースもあるかもしれません。

C#:refの引数を含むデリゲートに入れるラムダ式では型を省略できない話

文法に関する細かいメモ。

delegate void myDelegate(ref int test);

static void Main(string[] args)
{
    myDelegate del = (ref i) => i++;
}

上記のコードは構文エラーが出てしまいます。 引数にrefがある場合は、型を省略できないようです。

static void Main(string[] args)
{
    myDelegate del = (ref int i) => i++;
}

delegate void myDelegate(ref int test);

上記コードは正常にビルドが通ります。 略式の書き方に慣れすぎたせいか、ちょっと引っかかってしまいました…

C#:キャプチャした変数が解放されないパターンについて

    class Program
    {
        static void Main(string[] args)
        {
            localMethod();

            MyAction();

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.ReadLine();
        }

        private static void localMethod()
        {
            MyClass myClass = new MyClass();

            MyAction += () =>
            {
                //ラムダ式内でローカル変数myClassを参照
                myClass.Method();
            };
        }

        public class MyClass
        {
            public void Method()
            {
                Console.WriteLine("Closure");
            }

            ~MyClass()
            {
                Console.WriteLine("Destructor");
            }
        }

        private static Action MyAction;
    }

結果:

Closure

上記処理では、localMethodメソッド内でnewしたMyClassがMyActionに参照されたため、 オブジェクトの寿命がMyActionに引きずられることでデストラクタが呼び出されません。

対策として、Main関数で下記のようにMyActionにnullを代入すればデストラクタが読みだされます。

        static void Main(string[] args)
        {
            localMethod();

            MyAction();

            //null代入を追加
            MyAction = null;

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.ReadLine();
        }

結果:

Closure

Destructor

なお、ラムダ式を直接-=演算子で代入してもメソッド登録を解除できないため、MyActionの宣言が

private static event Action MyAction;

であった場合は、別のActionオブジェクトに=でラムダ式を格納し、そのラムダ式でMyActionに+=で登録、-=で登録解除するといった工夫が必要そうです。