RunningCSharp

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

Xamarin:ItemsControl.ItemsSourceプロパティとバインドした配列を別スレッドから動かす

WPFでもあった問題なのですが、(async/awaitなどを用いるなどして)メインではないスレッドから、ItemsControlのItemsSourceプロパティにバインドしたObservableCollectionに操作を行おうとするとエラーとなります。

(上記エラーはUWPプロジェクト、Windows8.1プロジェクトで確認)

xaml(View)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="asyncawait_test.Page1"
             xmlns:local="clr-namespace:asyncawait_test">
  <ContentPage.BindingContext>
    <local:ViewModel />
  </ContentPage.BindingContext>
    <StackLayout Orientation="Vertical">
      <Button Text="test" Command="{Binding MyCommand}" />
      <ListView ItemsSource="{Binding MyList}" />
    </StackLayout>  
</ContentPage>

ViewModel(一部抜粋)

public const string MyListPropertyName = "MyList";

private ObservableCollection<string> _myList = new ObservableCollection<string>();

/// <summary>
/// ListViewのItemsSource
/// </summary>
public ObservableCollection<string> MyList
{
    get
    {
        return _myList;
    }

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

        _myList = value;
        RaisePropertyChanged(MyListPropertyName);
    }
}

private RelayCommand _myCommand;

/// <summary>
/// ボタンのコマンド
/// </summary>
public RelayCommand MyCommand
{
    get
    {
        return _myCommand
            ?? (_myCommand = new RelayCommand(
                                    async () =>
                                    {
                                        await Task.Factory.StartNew(TestMethod);
                                    }));
    }
}

//awaitで呼び出されるため別スレッドで実行されるメソッド
private void TestMethod()
{
    //エラー
    MyList.Add("aaa");
}

WPFではDispacherを用いて解決しておりましたが、XamarinではDevice.BeginInvokeOnMainThreadメソッドを用いて解決するとよさそうです。

//awaitで呼び出されるため別スレッドで実行されるメソッド
private void TestMethod()
{
    //これならOK
    Device.BeginInvokeOnMainThread(() => MyList.Add("aaa"));
}

Xamarin+Azure:Mobile Apps Quickstart templateを使ってAzure Mobile Appを作成した話

埼玉・春日部で開催された「JXUG Xamarin もくもく会」に参加してきました。

ytabuchi.hatenablog.com

上記企画のTシャツがほしいからと、

ytabuchi.hatenablog.com

上記のリンクの通りにAzureのアプリケーションを作り、Xamarinで動くサンプルをダウンロードし、動作している動画をTwitterに投稿しました。

twitter.com

とても手軽にAzureに接続してデータ送信できるアプリケーションを作成できて感動しました。

なお私は以前Azureのお試し登録を行ってしまっていた為初回登録時の2万円分くらいの無料枠がないため、

無料のVisual Studio Dev Essentialsで、毎月3000円のAzure無料枠を活用しようsatonaoki.wordpress.com

上記の方法で月3000円の無料枠がもらえる形で登録しなおしました。

(初回の無料枠を使い切った状態から直接登録しようとしたときはうまくいかず、一度従量課金サブスクリプションを作ってからだと登録できました。)

会場で企画の紹介や手順の解説をして下さった皆様、有難う御座いました。

C#+XAML:MVVM Light Toolkitの.net Framework4.5(C#5)以上用カスタムスニペット(Setメソッドを利用するプロパティ用)

前回の記事では.net4.5向けのmvvminpcスニペットを作成しましたが、 Setメソッド(プロパティ変更+変更通知を行うViewModelBaseのメソッド)を用いたプロパティ用スニペットについても.net4.5用のオーバーライドに対応したものがなさそうだったため、作り方を記載しようと思います。

1;以下の内容で「mvvmInpcSet45.snippet」というファイルを用意します。

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
 <CodeSnippet Format="1.0.0">
   <Header>
     <SnippetTypes>
       <SnippetType>Expansion</SnippetType>
     </SnippetTypes>
     <Title>INPC "Set" Property</Title>
     <Shortcut>mvvminpcset45</Shortcut>
   </Header>
   <Snippet>
     <Declarations>
       <Literal Editable="true">
         <ID>Type</ID>
         <ToolTip>Property type</ToolTip>
         <Default>bool</Default>
         <Function>
         </Function>
       </Literal>
       <Literal Editable="true">
         <ID>AttributeName</ID>
         <ToolTip>Attribute name</ToolTip>
         <Default>_myProperty</Default>
         <Function>
         </Function>
       </Literal>
       <Literal Editable="true">
         <ID>InitialValue</ID>
         <ToolTip>Initial value</ToolTip>
         <Default>false</Default>
         <Function>
         </Function>
       </Literal>
       <Literal Editable="true">
         <ID>PropertyName</ID>
         <ToolTip>Property name</ToolTip>
         <Default>MyProperty</Default>
         <Function>
         </Function>
       </Literal>
     </Declarations>
     <Code Language="csharp"><![CDATA[private $Type$ $AttributeName$ = $InitialValue$;

       /// <summary>
       /// Sets and gets the $PropertyName$ property.
       /// Changes to that property's value raise the PropertyChanged event. 
       /// </summary>
       public $Type$ $PropertyName$
       {
           get
           {
               return $AttributeName$;
           }
           set
           {
               Set(ref $AttributeName$, value);
           }
       }]]></Code>
   </Snippet>
 </CodeSnippet>
</CodeSnippets>

2:1で用意したファイルを「%UserProfile%¥Documents¥Visual Studio 2015¥Code Snippets¥Visual C#¥My Code Snippets」に保存します。 または、Visual Studinのメニュー「ツール > コード スニペット マネージャー」より、インポート機能でスニペットを追加します。

3:Visual StudioC#ファイル内で「mvvmInpcSet45」と入力し、タブキーを押してみてください。(インテリセンスに表示されない場合は、Visual Studioを再起動してください)

上記の操作で、下記のような感じのスニペットが展開されたかと思います。

       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
           {
               Set(ref _myProperty, value);
           }
       }

.net4.5以降向けのすっきりしたSetメソッド用スニペットとなりました。

C#+XAML:MVVM Light Toolkitの.net Framework4.5(C#5)以上用カスタムスニペット(プロパティ用)

MVVMLight Toolkit純正のスニペット(mvvminpcやmvvminpclambdaなど)では、文字列かラムダ式でプロパティ名を渡しています。 しかし、.net4.5(C#5)ではnameof演算子を使い、メソッド側で呼び元のプロパティ名を取得できるため、実際のところ呼び出し時にプロパティ名は渡す必要がありません。

そのため.net4.5以降のMVVMLight Toolkitでは、RaisePropertyChangedメソッドにnameof演算子を使って実装されたプロパティ名関連の引数を無くしたオーバーロードが実装されています。

今回はmvvminpcをカスタムし、.net4.5以降のMVVMLight Toolkit向けの簡略化されたスニペットの作り方を紹介いたします。(なお、ファイルパスなどの情報はVisual Studio 2015を対象としたものです。)

1;以下の内容でファイルを用意します。名前は「mvvmInpc45.snippet」としておいてください。

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>INPC Property</Title>
      <Shortcut>mvvminpc45</Shortcut>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="true">
          <ID>Type</ID>
          <ToolTip>Property type</ToolTip>
          <Default>bool</Default>
          <Function>
          </Function>
        </Literal>
        <Literal Editable="true">
          <ID>AttributeName</ID>
          <ToolTip>Attribute name</ToolTip>
          <Default>_myProperty</Default>
          <Function>
          </Function>
        </Literal>
        <Literal Editable="true">
          <ID>InitialValue</ID>
          <ToolTip>Initial value</ToolTip>
          <Default>false</Default>
          <Function>
          </Function>
        </Literal>
        <Literal Editable="true">
          <ID>PropertyName</ID>
          <ToolTip>Property name</ToolTip>
          <Default>MyProperty</Default>
          <Function>
          </Function>
        </Literal>
      </Declarations>
      <Code Language="csharp"><![CDATA[private $Type$ $AttributeName$ = $InitialValue$;

        /// <summary>
        /// Sets and gets the $PropertyName$ property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public $Type$ $PropertyName$
        {
            get
            {
                return $AttributeName$;
            }

            set
            {
                if ($AttributeName$ == value)
                {
                    return;
                }

                $AttributeName$ = value;
                RaisePropertyChanged();
            }
        }]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

2:1で用意したファイルを「%UserProfile%\Documents\Visual Studio 2015\Code Snippets\Visual C#\My Code Snippets」に保存します。 または、Visual Studinのメニュー「ツール > コード スニペット マネージャー」より、インポート機能でスニペットを追加します。

3:Visual StudioC#ファイル内で「mvvminpc45」と入力し、タブキーを押してみてください。(インテリセンスに表示されない場合は、Visual Studioを再起動してください)

上記の操作で、下記のような感じのスニペットが展開されたかと思います。

    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();
        }
    }

mvvminpcよりもすっきりした内容で、.net4.5以降では同機能が利用できます。

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