WPFコントロールテンプレートとデータテンプレートの違い

WPFのコントロールは、

  1. CheckBoxのような単一のアイテムを保有するコンテンツコントロール
  2. ListBoxのような複数のアイテムを保有するアイテムコントロール
  3. Gridのようなレイアウトを制御するパネルコントロール

の3種があります。今回は1, 2のコンテンツコントロール、アイテムコントロールを対象とした『テンプレート』についてのお話。コンテンツコントロール、アイテムコントロールいずれも両テンプレートをそれぞれ以下のプロパティ名で公開しています。

template_property

以下Buttonを例に、簡単に違いを見ておきます。

 

1.コントロールテンプレート

コントロールテンプレートは「コントロール自身の見た目を決定するもの」と言えます。良く言われる話ですがWPFのコントロールはルックレス[LookLess]、つまり基本的には見た目を持ちません。

そのため通常のButtonは、 normal_button_image←こんなですが、

内部的には normal_button←のような構成を取っています。

この一番下部にあるButtonがWPFのButtonです。ButtonChromeやContentPresenterが見た目を制御していると言って良いでしょう。

見た目をいじったものとして、control_template_image←こんなButtonがすぐ作れますが、これは

内部的にはcontent_template←のような構成を取っています。

これを構成するXAMLを見ておくと、

<Button>
    コンテンツ
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                <ContentPresenter/>
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>

こんな感じ。ControlTemplateにターゲットタイプを指定していることに注意、これはContentPresenterが動作するために必要です。というのもContentPresenterは内部でターゲットタイプを必要とするBindingを持っているため。ContentPresenterを持たないボタンなら不要です。TemplateBindingはこのControlTemplateが割り当てられる先(つまりButton)のプロパティと同じ値を取ります、というものです。上の例で見るように、Buttonを例にするとコントロールテンプレートとは、『ボタンとは四角くて、周囲が少し縁取られており、マウスがホバーされると色が変わり、コンテンツをコントロールの中心に持った…』という部分をカスタマイズするものです。

ちなみに上で示したButtonのテンプレート構成図は

var control = Application.Current.FindResource(typeof(Button));
using(XmlTextWriter writer = new XmlTextWriter(@"DefaultButtonTemplate.xml", Encoding.UTF8)){
    writer.Formatting = Formatting.Indented;
    XamlWriter.Save(control, writer);
}
Console.WriteLine(XamlWriter.Save(this));

こんなコードで出力することができます。

 

 

2.データテンプレート

データテンプレートは「コントロールに割り当てられたデータの見た目を定義するもの」と言えます。コントロールテンプレートがボタンの”コントロール”としての部分をカスタマイズするものなのに対し、データテンプレートはそれに表示されるコンテンツの部分をカスタマイズするものです。

私的な印象では、データテンプレートの99% はアイテムコントロールで使われるのですが(リスト形式のデータを表現するのに便利だから)、コンテンツコントロールにもムリヤリ適用するとこんな感じ→ datatemplate_image

コントロールテンプレートはいじってないのでボタンらしい感じながら、表示されているものがカスタマイズされた感じなことに気付くと思います。これを実現するデータテンプレートは参考までにこんな感じです。

<Button x:Name="dataTemplatedButton" Margin="50,50,50,50">
    <Button.ContentTemplate>
        <DataTemplate>
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding}" FontSize="12"/>
                <TextBlock Text="{Binding}" FontSize="8"/>
            </StackPanel>
        </DataTemplate>
    </Button.ContentTemplate>
</Button>

上のような「サイズ違いで2コ同じコンテンツを並べたい」等の場合はデータテンプレートを使います。

 

3.どちらを使うか

コントロールテンプレートは多くの場合TemplateBindingを使い、テンプレート親とのコントロールとしてのデータとのバインドを作成します。一方でデータテンプレートはBindingを使い、割り当てられたデータとのバインドを作成します。カスタマイズしたいものがコントロール自身なのか、割り当てられるデータなのか、でどちらを使うかが決まります。

状況によっては、コントロールテンプレート、データテンプレートどちらを使っても可能なことがあります。この場合これらテンプレートを再利用する時のことを考えると良いと思います。そのカスタマイズした結果できるであろうコントロールに、別のデータを載せることがあるのかどうか、を考えてみてください。別のデータを載せることもある場合はコントロールテンプレート、別のデータを載せない場合はデータテンプレートと判断してよいかと思います。

まぁコンテンツコントロールの場合はコントロールテンプレート、アイテムコントロールの場合はデータテンプレートと言ってもかなりの割合で正しい選択になるとは思います;

広告

WCF”ビヘイビア”なるもの

WPFでのビヘイビアとは、Viewに関連する振る舞い(Behavior)を再利用可能な形で作成したものでした。WCFにもビヘイビアというものがありWCFでのビヘイビアとは、WCFでの通信時の特定のタイミングで何らかの振る舞い(Behavior)を発動させるためのものです。

WPFのビヘイビアはコミュニティの間で「そう呼ばれているもの」 (それがBlendで採用され公にはなっている)ですが、WCFのビヘイビアはそういうクラスがあり「もともと正式なもの」という違いがあります。

 

1.WCFビヘイビアの種類

WCFのビヘイビアとは大きく下記4種あるようです。

①サービスビヘイビア(IServiceBehaviorを実装する)
サーバ側に追加する機能追加モジュールのこと。WSDLファイルによりサーバが提供する機能を公開することができるが、サービスビヘイビアの一種であるserviceMetadataビヘイビアを使うとこのWSDLファイル作成を自動作成&公開できる。(このWSDLファイルを公開するエンドポイントがMEXエンドポイントであり、これがあるとユーザは簡単にプロキシクラスや構成ファイルが作成できるようになる)

②エンドポイントビヘイビア(IEndpointBehaviorを実装する)
サービスビヘイビアがサーバ側に対するビヘイビアだったのに対し、エンドポイントビヘイビアはエンドポイント単位のビヘイビアです。ただし良い実例を見かけません;

③コントラクトビヘイビア(IContractBehaviorを実装する)
コントラクト単位でのビヘイビアです、こちらも良い実例を見かけません;

④操作ビヘイビア(IOperationBehaviorを実装する)
Interfaceとして定義するWCFサービスにはOperationContract属性をつけます。このInterfaceのメソッドに特性を追加するのが操作ビヘイビアです。代表例として、トランザクション操作を実装する操作ビヘイビアがあります(OperationBehaviorにTransactionScopeRequired=trueとして指定)。

これらのスコープや、構成方法についてはこちらを参照ください。

 

2.WCFビヘイビアの利用

上記①に記載したMEXエンドポイントの公開方法を見ておきます。迷子にならないようにまず全体のプロジェクト・ファイル構成を見ておきます(下図)。いずれもコンソールアプリです。

project
サービスを公開するためサーバー側から作成します。まずはコントラクトから。

コントラクトとしての、ISampleService.cs

namespace WcfBehavior.TestServer
{
    using System.ServiceModel;
    [ServiceContract]
    interface ISampleService
    {
        [OperationContract]
        string Get();
    }
}

これを実際に実装する、TestService.cs

namespace WcfBehavior.TestServer
{
    using System;

    class TestService : ISampleService
    {
        public string Get()
        {
            return DateTime.Now.ToString();
        }
    }
}

App.Config<without MEX>

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <system.serviceModel>
    <services>
      <service name="WcfBehavior.TestServer.TestService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8000/WcfBehavior"/>
          </baseAddresses>
        </host>
        <endpoint address ="WcfBehaviorTest"
                  binding="basicHttpBinding"
                  contract="WcfBehavior.TestServer.ISampleService"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>

これにMEXを追加した、App.config<with MEX>

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <system.serviceModel>
    <services>
      <service name="WcfBehavior.TestServer.TestService"
               behaviorConfiguration ="SampleMexBehavior">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8000/WcfBehavior"/>
          </baseAddresses>
        </host>
        <endpoint address ="WcfBehaviorTest"
                  binding="basicHttpBinding"
                  contract="WcfBehavior.TestServer.ISampleService"/>
        <endpoint address="WcfBehaviormex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="SampleMexBehavior">
          <serviceMetadata httpGetEnabled="True"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

MEXはWSDLを送信するエンドポイントなのでエンドポイントとして定義が必要です。また公開されるISampleServiceはWSDLをHttp経由で取得できるようにする、というビヘイビアを設定しています(上記、behaviorConfiguration)。最後にサーバ側メイン処理。

 Program.cs(サーバー側)

namespace WcfBehavior.TestServer
{
    using System;
    using System.ServiceModel;
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(TestService));
            host.Open();
            Console.WriteLine("Press Enter to Exit.");
            Console.ReadLine();
        }
    }
}

このサーバーConsoleアプリを管理者として実行した状態で、クライアント側作成に移ります。

クライアント側ではサーバで公開したMEXエンドポイントを利用します。まず、プロジェクトを右クリックして、[追加] – [サービスの追加] を開き、アドレスを指定して [移動] を押すと

add_service

このようにサービスが見つかります。サービスが見つからない場合、

  • サーバーが起動状態か?
  • MEXエンドポイントが公開されているか?

を確認ください。名前空間は今回このままServiceReference1としておきますが、これでOkを押すとApp.configが自動的に更新され、プロキシクラスも作成されます(ぱっと見見えません)。これの使い方は非常に簡単!

Program.cs(クライアント側)

namespace WcfBehavior.TestClient
{
    using System;
    class Program
    {
        static void Main(string[] args)
        {
            var proxy = new ServiceReference1.SampleServiceClient();
            Console.WriteLine(proxy.Get());
            Console.WriteLine("Press Enter to Exit.");
            Console.ReadLine();
        }
    }
}

サービス名+Clientがプロキシクラスの名前になるようです。

MEXの説明がメインになりましたが、WCFビヘイビアを使うことで他にも、同時実行数や同時呼び出し数を制限したり,セキュリティを設けたり,通信時のログをとったり、といった目的で使えます。

 

3.ビヘイビアの自作

MSDNに詳しく記載があります。どのビヘイビアを作るか?によってInterfaceが決まりますが、今回は何もしないサービスビヘイビアを作ってみます。サーバ側に下記2ファイルを追加します。

CustomBehavior.cs

namespace WcfBehavior.TestServer
{
    using System;
    using System.Collections.ObjectModel;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Channels;

    public class CustomBehavior : IServiceBehavior
    {
        public void AddBindingParameters(ServiceDescription serviceDescription, 
            ServiceHostBase serviceHostBase, 
            Collection<ServiceEndpoint> endpoints, 
            BindingParameterCollection bindingParameters)
        {
            Console.WriteLine("AddBindingParameters has called");
        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
            ServiceHostBase serviceHostBase)
        {
            Console.WriteLine("CustomBehavior has applied.");
        }

        public void Validate(ServiceDescription serviceDescription, 
            ServiceHostBase serviceHostBase)
        {
            Console.WriteLine("Validate has called.");
        }
    }
}

このビヘイビアを構成ファイルから使えるようにするためにもう一つ、下記クラスが必要となります。

CustomBehaviorElement.cs

namespace WcfBehavior.TestServer
{
    using System;
    using System.ServiceModel.Configuration;
    public class CustomBehaviorElement : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get { return typeof(CustomBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new CustomBehavior();
        }
    }
}

このクラスを作る場合、System.Configurationへの参照が必要になります。これを構成ファイルから使うようにするにはサーバー側のApp.configを以下のように変更します。

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <system.serviceModel>
    <services>
      <service name="WcfBehavior.TestServer.TestService"
               behaviorConfiguration ="SampleMexBehavior">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8000/WcfBehavior"/>
          </baseAddresses>
        </host>
        <endpoint address ="WcfBehaviorTest"
                  binding="basicHttpBinding"
                  contract="WcfBehavior.TestServer.ISampleService"/>
        <endpoint address="WcfBehaviormex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="SampleMexBehavior">
          <serviceMetadata httpGetEnabled="True"/>
          <customBehavior/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <extensions>
      <behaviorExtensions>
        <add name="customBehavior"
             type="WcfBehavior.TestServer.CustomBehaviorElement, WcfBehavior.TestServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
  </system.serviceModel>
</configuration>

カスタムビヘイビアの場合、extensionsタグを作って、その中にクラスとビヘイビア名を追加しておく必要があります。こうすることで少し上のserviceBehaviorsでカスタムビヘイビアを参照できるようになります。これは一番上でサービスのbehaviorConfigurationとして指定されているのでサービスへと適用される訳です。

最後になりましたが、これを動かしたのは以下。

activate_custombehavior

サーバ側でConsoleへの出力が確認できると思います(これはクライアントを起動させずにサーバ側起動直後に全文出力されます)。

WCFによるP2Pチャットソフト

やってみたかったWCFでP2P。P2Pの利用法としてはベタなチャットソフト作成して方法を確認しておきます。

1.通信内容を決める

WCFで通信するときはコントラクトとして通信内容をInterfaceで実装するんでした、こちら。

 ITocsChat.cs

namespace WcfP2PChat
{
    using System.ServiceModel;
    [ServiceContract(CallbackContract=typeof(ITocsChat))]
    public interface ITocsChat
    {
        [OperationContract(IsOneWay = true)]
        void Join(string userName);

        [OperationContract(IsOneWay = true)]
        void Chat(string userName, string message);

        [OperationContract(IsOneWay = true)]
        void Leave(string userName);
    }
}

このInterfaceとP2Pの送信・応答チャネル定義をするIClientChannelの2つのInterfaceをもつInterfaceを定義しておきます。P2Pではこの2つのInterafaceを持つほうを使います。ただし、このInterfaceクラスを明示的に実装することはありません。

 ITocsChatChannel.cs

namespace WcfP2PChat
{
    using System.ServiceModel;
    public interface ITocsChatChannel : ITocsChat, IClientChannel
    {
    }
}

 

2.P2Pのエンドポイントを構成する

コードからでも問題ないと思いますが、今回はアプリケーション構成ファイルを使います。

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
      <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <system.serviceModel>
    <client>
      <endpoint name="TocsSimpleChatApp"
                address="net.p2p://TocsMesh/TocsChat"
                binding="netPeerTcpBinding"
                bindingConfiguration="TocsChatBinding"
                contract="WcfP2PChat.ITocsChat"/>
    </client>
    <bindings>
      <netPeerTcpBinding>
        <binding name="TocsChatBinding" port="0">
          <resolver mode="Auto"/>
          <security mode="None"/>
        </binding>
      </netPeerTcpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

ChatForm.cs(抜粋)

    public partial class ChatForm : Form, ITocsChat
    {
        private ITocsChatChannel m_participant;

        public ChatForm()
        {
            InitializeComponent();

            var site = new InstanceContext(this);
            var binding = new NetPeerTcpBinding("TocsChatBinding");
            var channelFactory = new DuplexChannelFactory<ITocsChatChannel>(site, "TocsSimpleChatApp");
            m_participant = channelFactory.CreateChannel();

            var statusHandler = m_participant.GetProperty<IOnlineStatus>();
            statusHandler.Online += statusHandler_Online;
            statusHandler.Offline += statusHandler_Offline;
        }
    }

多くの方に理解して頂けるようWindows Forms にしました。FormはITocsChatを実装するようにしていますが、これは他ユーザから送られてきたメッセージをFormで処理します、ということを表しています。またm_participantとしてITocsChannelオブジェクトを取得していますが、これは他ユーザ(群)を表していると考えて良いと思います。

 

3.チャットソフトを作成する

あとはこまごまとした作りこみを少々。本来なら状態に応じてコントロールの有効/無効切り替えや日時表示etc..機能を盛り込むでしょうが、わかりやすさ重視で実装するとこんな感じ。

chat

ChatForm.cs(全体) 

namespace WcfP2PChat
{
    using System;
    using System.Windows.Forms;
    using System.ServiceModel;

    public partial class ChatForm : Form, ITocsChat
    {
        private ITocsChatChannel m_participant;

        public ChatForm()
        {
            InitializeComponent();

            var site = new InstanceContext(this);
            var binding = new NetPeerTcpBinding("TocsChatBinding");
            var channelFactory = new DuplexChannelFactory<ITocsChatChannel>(site, "TocsSimpleChatApp");
            m_participant = channelFactory.CreateChannel();

            var statusHandler = m_participant.GetProperty<IOnlineStatus>();
            statusHandler.Online += statusHandler_Online;
            statusHandler.Offline += statusHandler_Offline;
        }

        void statusHandler_Online(object sender, EventArgs e){
            textBoxChatLog.Text += "接続しました" + Environment.NewLine;
        }
        void statusHandler_Offline(object sender, EventArgs e){
            textBoxChatLog.Text += "切断されました" + Environment.NewLine;
        }

        public void Join(string userName){
            textBoxChatLog.Text += userName + "さんが参加されました" + Environment.NewLine;
        }
        public void Chat(string userName, string message){
            textBoxChatLog.Text += userName + ":" + message + Environment.NewLine;
        }
        public new void Leave(string userName){
            textBoxChatLog.Text += userName + "さんが退出されました" + Environment.NewLine;
        }

        private void buttonJoin_Click(object sender, EventArgs e){
            m_participant.Join(textBoxUserName.Text);
        }
        private void buttonLeave_Click(object sender, EventArgs e){
            m_participant.Leave(textBoxUserName.Text);
        }
        private void buttonSay_Click(object sender, EventArgs e){
            m_participant.Chat(textBoxUserName.Text, textBoxSay.Text);
        }

    }
}

Formが実装するITocsChat Interfaceは他ユーザからの操作に対するcallbackとなるので上のような処理となります。これを動かした模様はこちら。

chatting

こんなに少ないコードながらに結構まともに見えます。

 

補足 – WindowsXPの場合

上記のようにWCFのP2Pでデフォルトリゾルバを使った場合、PNRP Ver2.0となりますがこれはWinXP sp3もしくはKB920342を入れる必要があるようです(こちら)。またPNRPがIPv6の上に構築されるのでIPv6もインストール必要です(こちら)。少なくともこれらが必要なのは間違いありませんが、それで正しく動くかは確認できていません。

バックグランド検索をするWPF TextBox

以前WPFで入力補完が効くTextBoxを作成してみました。似たところで、入力の度にバックグランドで非同期タスクにより検索を走らせる処理を考えてみます。

 

form

簡単にはこんなところでしょうか。

MainWindow.xaml.cs

<Window x:Class="BackgroundSearchTextBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="174" Width="334">
    <Grid>
        <Label Content="検索ワード" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
        <TextBox x:Name="SearchWordTextBox" HorizontalAlignment="Left" Height="23" Margin="95,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="201"/>
        <Label Content="検索結果" HorizontalAlignment="Left" Margin="10,43,0,0" VerticalAlignment="Top"/>
        <TextBlock x:Name="SearchResultTextBlock" HorizontalAlignment="Left" Margin="95,43,0,0" TextWrapping="Wrap" Text="Enter Keyword" VerticalAlignment="Top" Height="91" Width="201"/>
    </Grid>
</Window>

MainWindow.xaml

namespace BackgroundSearchTextBox
{
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows;

    public partial class MainWindow : Window
    {
        private string[] candidates = new string[]{
            "apple",
            "banana",
            "orange",
            "grape",
            "pineapple",
            "mango"
        };

        public MainWindow()
        {
            InitializeComponent();

            // Rxなし
            SearchWordTextBox.TextChanged += (s, e) =>
            {
                Task.Factory.StartNew(
                    search =>
                    {
                        string searchWord = search as string;
                        if (String.IsNullOrEmpty(searchWord)) return "Enter Keyword.";
                        var candidatesQuery = candidates.Where(cand => cand.StartsWith(searchWord));
                        return candidatesQuery.IsEmpty() ? "No Result." : candidatesQuery.Aggregate((a, b) => a + Environment.NewLine + b);
                    },
                    SearchWordTextBox.Text)
                .ContinueWith(t => SearchWordTextBox.Dispatcher.Invoke(() => SearchResultTextBlock.Text = t.Result));
            };

        }
    }
}

これを動かすにはNugetパッケージインストーラから

PM> install-package ix-main

としてInteractive Extensionsをインストールしておく必要があります。

上記はイマイチ。文字入力の度にタスクを起動させていますが、頻繁な文字入力の度にタスクを生成してたら結構オーバーヘッドが大きいです。そこでRxを使ってみます。

 

MainWindow.xaml.cs

namespace BackgroundSearchTextBox
{
    using System;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Reactive.Linq;
    using System.Reactive.Concurrency;

    public partial class MainWindow : Window
    {
        private string[] candidates = new string[]{
            "apple",
            "banana",
            "orange",
            "grape",
            "pineapple",
            "mango"
        };

        public MainWindow()
        {
            InitializeComponent();

            // Rxあり
            Observable.FromEvent<TextChangedEventHandler, TextChangedEventArgs>(
                action => (s, e) => action(e),
                handler => SearchWordTextBox.TextChanged += handler,
                handler => SearchWordTextBox.TextChanged -= handler)
                .Select(_ => SearchWordTextBox.Text)
                .Throttle(TimeSpan.FromMilliseconds(100))
                .Select(searchWord =>
                    {
                        if (String.IsNullOrEmpty(searchWord)) return "Enter Keyword.";
                        var candidatesQuery = candidates.Where(cand => cand.StartsWith(searchWord));
                        return candidatesQuery.IsEmpty() ? "No Result." : candidatesQuery.Aggregate((a, b) => a + Environment.NewLine + b);
                    }
                )
                .ObserveOn(new DispatcherScheduler(App.Current.Dispatcher))
                .Subscribe(t => SearchResultTextBlock.Text = t);

        }

    }
}

Rxあり版を動かすためにはNugetパッケージマネージャーから

PM> install-package rx-main
PM> install-package rx-wpf

としてReactive Extensions(Rx)のインストールも必要です。Rxを使うことで、

  • ちゃんとイベントリッスン解除ができる
  • 100ms間値が落ち着くのを待つことができる
  • 全体的に、特にスレッド行き来する処理の記述が容易&見やすい

と言えますね。まだまだ学習が必要だ・・。

ReactiveUI を WPFで使う

ReactiveUIはWindowsFormsでも使えるMVVMフレームワークであり、先日使ってみました。単純なバインドの例ですが、MVC, MVP, MVVM等のどのパターンを使うかの選択についてはフレームワークサポートの影響が非常に大きいため、ViewとViewとModelの間に来る部分(PresenterやViewModelなど)との連携をどうサポートしてくれるかの一例を見た次第です。

ReactiveUIをWindows Formsで使った場合は前回ご紹介したので、蛇足ながらにWPFで使う場合の例を一応見ておきます。ViewModelは前回と同じです、

SampleViewModel.cs

namespace ReactiveUIInWpf
{
    using System;
    using ReactiveUI;

    public class SampleViewModel : ReactiveObject
    {
        private string inputString1;
        public string InputString1 {
            get { return inputString1; }
            set { this.RaiseAndSetIfChanged(ref inputString1, value); }
        }

        private string inputString2 = "input2の初期値です";
        public string InputString2 {
            get { return inputString2; }
            set { this.RaiseAndSetIfChanged(ref inputString2, value); }
        }

        private DateTime currentDateTime = DateTime.Now;
        public DateTime CurrentDateTime {
            get { return currentDateTime; }
            set { this.RaiseAndSetIfChanged(ref currentDateTime, value); }
        }

        // 初期化コマンド
        public ReactiveCommand<object> Initialize{ get; set;}

        public SampleViewModel()
        {
            // 初期化コマンドの作成
            Initialize = ReactiveCommand.Create(this.WhenAny(vm => vm.InputString1, str => true)); // =常時有効
            Initialize.Subscribe(_ =>
            {
                InputString1 = "初期値1";
                InputString2 = "初期値2";
                CurrentDateTime = new DateTime(2020, 1, 1);
            }
            );
        }

        public override string ToString(){
            return "1:" + InputString1 + Environment.NewLine +
                   "2:" + InputString2 + Environment.NewLine +
                   CurrentDateTime.ToString();
        }
    }
}

これに対するViewはWPFではXAMLで記述するので、

MainWndow.xaml

<Window x:Class="ReactiveUIInWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="217" Width="291">
    <Grid>
        <Label Content="User Input1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
        <Label Content="User Input2" HorizontalAlignment="Left" Margin="10,43,0,0" VerticalAlignment="Top"/>
        <Label Content="Time" HorizontalAlignment="Left" Margin="10,76,0,0" VerticalAlignment="Top"/>
        <TextBox Text="{Binding InputString1}" HorizontalAlignment="Left" Height="23" Margin="108,12,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="155"/>
        <TextBlock Text="{Binding InputString2}" HorizontalAlignment="Left" Margin="108,48,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="165"/>
        <DatePicker SelectedDate="{Binding CurrentDateTime}" HorizontalAlignment="Left" Margin="108,76,0,0" VerticalAlignment="Top" Width="155"/>
        <Button Content="Execute" Command="{Binding Initialize}" HorizontalAlignment="Left" Margin="45,128,0,0" VerticalAlignment="Top" Width="75" Height="34"/>
        <Button Content="Close" HorizontalAlignment="Left" Margin="188,128,0,0" VerticalAlignment="Top" Width="75" Height="34"/>
    </Grid>
</Window>

MainWindow.xaml.cs

namespace ReactiveUIInWpf
{
    using System.Windows;

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new SampleViewModel();
        }
    }
}

reactiveui_wpf

前回との差分を見てください。WindowsFormsとWPFとでViewの部分が独立しただけでModel(今回作っていません)とViewModelまでが共用化できていることになります。ReactiveUIはまだこれから色々みていきたいと思っています。

 

VCメモリリーク検出ツール

VC++でメモリリークチェックツールはいくつかありますが私的に手放せない一品をご紹介。

Visual Leak Detectorです。インストールはOk連打でOk↓

install

途中Visual Studioとの連携を取るか確認されますのでもちろんチェックしておきます。

setup

使い方は至極簡単。どこかのcppファイル内で#include vld.h”を足すだけ。

 Test.cpp

#include "stdafx.h"
#include "vld.h"

int _tmain(int argc, _TCHAR* argv[])
{
	int* p = new int[256];
	return 0;
}

デバッグモードのみ自動的にライブラリがリンクされ、プログラム終了後出力ウィンドウに下のような出力がでます。

< 出力ウィンドウ>

Visual Leak Detector Version 2.4RC2 installed.
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 1 at 0x006A31A0: 1024 bytes ----------
  Leak Hash: 0x2741707D, Count: 1, Total 1024 bytes
  Call Stack (TID 6308):
    0x0F27C260 (File and line number not available): MSVCR120D.dll!operator new
    c:\work\memoryleakchecksample\memoryleakchecksample\memoryleakchecksample.cpp (9): MemoryLeakCheckSample.exe!wmain + 0xA bytes
    f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c (623): MemoryLeakCheckSample.exe!__tmainCRTStartup + 0x19 bytes
    f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c (466): MemoryLeakCheckSample.exe!wmainCRTStartup
    0x7708338A (File and line number not available): kernel32.dll!BaseThreadInitThunk + 0x12 bytes
    0x77979F72 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x63 bytes
    0x77979F45 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x36 bytes
  Data:
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........


Visual Leak Detector detected 1 memory leak (1060 bytes).
Largest number used: 1060 bytes.
Total allocations: 1060 bytes.
Visual Leak Detector is now exiting.
プログラム '[6304] MemoryLeakCheckSample.exe' はコード 0 (0x0) で終了しました。

素晴らしい、そして美しい。