2013年10月06日

WPFサンプル:バインディング時のソースへの更新を遅延させる

   このエントリーをはてなブックマークに追加 Clip to Evernote
今回は、双方向バインディング時のソースへの更新を遅延させる方法を。

双方向のバインディングを指定した場合でも、なんらかのイベントが発生するまで、 バインディングソースへの反映を遅延させたい場合があります。
例えば、複合的なデータ検証が終了するまでは、バインディングソースのオブジェクトへ、 変更を反映させたくないといったケースです。
そのようなときに利用するのが、UpdateSourceTriggerプロパティです。
このプロパティの値をUpdateSourceTrigger.Explicitにすることで、 明示的にBindingExpressionクラスのUpdateSourceメソッドを呼び出したときに、データソースを更新するという指定になります。

INotifyPropertyChangedを実装するで取り上げたサンプルを少し変更して、この UpdateSourceTrigger の機能を確認するWPFのプログラムを作成してみます。

まずは、モデル側のクラス。

public class MyModel : INotifyPropertyChanged {
    private string _name;
    private string _webSite;
    private UpdateSourceTrigger _updateSourceTrigger = UpdateSourceTrigger.PropertyChanged;

    public string Name {
        get { return this._name; }
        set {
            if (value != this._name) {
                this._name = value;
                NotifyPropertyChanged("Name");
            }
        }
    }

    public string WebSite {
        get { return this._webSite; }
        set {
            if (value != this._webSite) {
                this._webSite = value;
                NotifyPropertyChanged("WebSite");
            }
        }
    }

    public UpdateSourceTrigger UpdateSourceTrigger {
        get { return this._updateSourceTrigger; }
        set {
            if (value != this._updateSourceTrigger) {
                this._updateSourceTrigger = value;
                NotifyPropertyChanged("UpdateSourceTrigger");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info) {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

INotifyPropertyChangedインターフェースを実装し、プロパティの更新通知ができるようにしています。
動的にUpdateSourceTriggerの値を変更したいのでモデル側にUpdateSourceTrigger型の プロパティを定義しています。このクラスはModelというよりは、ViewModelといったほうが いいですね。Modelが無いのにViewModelと言うのは変ですが...

次に、XAML。

<Window x:Class="UpdateSourceTriggerSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:UpdateSourceTriggerSample"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"               
        Title="MainWindow" Height="160" Width="300" >
    <Window.Resources>       
        <ObjectDataProvider x:Key="UpdateSourceTriggerValues" MethodName="GetValues"
                        ObjectType="{x:Type sys:Enum}"  >
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="UpdateSourceTrigger" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Window.DataContext>
        <my:MyModel x:Name="MyModel" Name="Google" WebSite="http://www.google.com"/>       
    </Window.DataContext>
    <StackPanel>
        <TextBox Name="textBox1" Text="{Binding Name, Mode=TwoWay,
            UpdateSourceTrigger=PropertyChanged}" />
        <TextBox Name="textBox2" Text="{Binding WebSite, Mode=TwoWay,
            UpdateSourceTrigger=PropertyChanged}"/>
        <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
            <Label Content="UpdateSourceTrigger:" />
            <ComboBox Width="130"
                ItemsSource="{Binding Mode=OneWay,
                     Source={StaticResource UpdateSourceTriggerValues}}"
                SelectedItem="{Binding UpdateSourceTrigger}"
                SelectionChanged="ComboBox_SelectionChanged"  VerticalAlignment="Center" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"
                    Margin="0,10,0,0">
            <Button Content="Modelの値を確認" Height="23" Name="button1" Width="110"
                Click="buttonConfirm_Click" />
            <Button Content="更新" Height="23" Name="button3" Width="75"
                Click="buttonUpdate_Click" />
        </StackPanel>
    </StackPanel>
</Window>

二つのTextBoxはそれぞれMyModelオブジェクトの、NameとWebSiteプロパティとバインドしています。
ComboBoxは、項目一覧は、UpdateSourceTrigger列挙型の列挙子とバインドしています。
このために、Window.Resourcesで ObjectDataProviderを定義しています。
そして選択された項目は、MyModelオブジェクトのUpdateSourceTriggerとバインドしています。

では、MainWindowのコードビハインドのコードです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace UpdateSourceTriggerSample {
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            _myObject = DataContext as MyModel;
        }

        private MyModel _myObject;

        private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if (_myObject == null)
                return;
            ChangeUpdateSourceTrigger(textBox1, "Name");
            ChangeUpdateSourceTrigger(textBox2, "WebSite");
        }

        private void ChangeUpdateSourceTrigger(TextBox textBox, string name) {
            Binding binding = BindingOperations.GetBinding(textBox1, TextBox.TextProperty);
            BindingOperations.SetBinding(textBox, TextBox.TextProperty,
                new Binding(name) {
                    Mode = BindingMode.TwoWay,
                    UpdateSourceTrigger = _myObject.UpdateSourceTrigger
                });
        }

        private void buttonConfirm_Click(object sender, RoutedEventArgs e) {
            MessageBox.Show(string.Format("{0} {1}", _myObject.Name, _myObject.WebSite));
        }
       
        private void buttonUpdate_Click(object sender, RoutedEventArgs e) {
            BindingExpression be = textBox1.GetBindingExpression(TextBox.TextProperty);
            be.UpdateSource();
            be = textBox2.GetBindingExpression(TextBox.TextProperty);
            be.UpdateSource();          
        }

    }
}

3つのイベントハンドラを定義しています。

ComboBox_SelectionChangedイベントハンドラ
コンボボックスの選択項目が変更されたときに、 TextBoxのTextプロパティに設定されているBindオブジェクトのUpdateSourceTriggerプロパティの 値を変更するためのものです。
データバインドでは対応できないため、イベントハンドラでBindオブジェクトのUpdateSourceTriggerプロパティの値を変えています。
初期値は、PropertyChangedですが、これをComboBoxでExplicitに変更したときに、 textBox1, textBox2で設定されているBindオブジェクトのUpdateSourceTriggerプロパティ値がExplicitに変更されることになります。

buttonConfirm_Clickイベントハンドラ
MyModelオブジェクトのNameとWebSiteプロパティの値をMesageBoxに表示します。

buttonUpdate_Clickイベントハンドラ
明示的にBindingExpressionクラスのUpdateSourceメソッドを呼び出すことで、textBox1とtextBox2のTextプロパティの値をMyModelオブジェクトに設定します。


以下、実行時のスクリーンショットです。

初期状態
UpdateSourceTrigger1

PropertyChanged時 : TextBoxを書き換えた後、「Modelの値を確認」ボタンを押す
UpdateSourceTrigger2

Explicit時 : TextBoxを書き換えた後、「Modelの値を確認」ボタンを押す
UpdateSourceTrigger3

Explicit時 :「更新」ボタンを押した後、再度「Modelの値を確認」ボタンを押す UpdateSourceTrigger4


ComboBoxの値がPropertyChangedの時には、TextBoxを書き換えた後、「Modelの値を確認」ボタンを押すと、Model側の値もTextBoxの値と同じになっていることが確認できます。
一方、ComboBoxの値がExplicitの時には、TextBoxを書き換えて「Modelの値を確認」ボタンを押しても、Model側の値が更新されません。「更新」ボタンを押し、明示的にModelの値を更新してから、 「Modelの値を確認」ボタンを押せば、Modelが更新されていることが確認できます。


WPFサンプル・目次
  

Posted by gushwell at 22:00Comments(5)TrackBack(0)

2013年09月30日

WPFサンプル:DataGridTemplateColumnでDataGridをカスタマイズする

   このエントリーをはてなブックマークに追加 Clip to Evernote
「DataGrid にコレクションをバインドする」「プロパティ値を変更してDataGridの見た目と動作を変更する」では、DataGridのColumnsプロパティには、テキストを表示するDataGridTextColumnを指定してきましたが、その他、DataGridCheckBoxColumn、DataGridHyperlinkColumn、DataGridComboBoxColumn、DataGridTemplateColumnなどを指定することができます。
今回は、DataGridCheckBoxColumnとDataGridTemplateColumnを使ったサンプルを書いてみました。

まずは、実行時のスクリーンショットをお見せします。

DataGrid31

DataGridにバインドするオブジェクト以下のとおりです。

    public class User {
        public string Nickname { get; set; }
        public string Name { get; set; }
        public DateTime Birthday { get; set; }
        public string Birthplace { get; set; }
        public bool IsRegular { get; set; }
    }

    public class UserList {
        public ObservableCollection<User> Users { get; set; }
        public UserList() {
            Users = new ObservableCollection<User> {
                new User { Name="芥川 隆乃輔", Nickname="ryu", 
                           Birthday= new DateTime(1950,3,6), Birthplace="東京"},
                new User { Name="名都目 漱石", Nickname="natsu", 
                           Birthday= new DateTime(1982,11,13), Birthplace="神奈川"},
                new User { Name="宮沢 健治", Nickname="miya", 
                           Birthday= new DateTime(1964,7,24), Birthplace="宮城"},
                new User { Name="島咲 藤村", Nickname="saki", 
                           Birthday= new DateTime(1971,2,5), Birthplace="長野"},
                new User { Name="紀久地 寛", Nickname="kan", 
                           Birthday= new DateTime(1963,12,15), Birthplace="長崎"},
            };
        }
    }

前回示したものほぼ同じですが、UserクラスにIsRegular プロパティを追加しています。

モデル側の定義ができましたので、次は、UI側の定義に移りましょう。
チェックボックスを表示するのに、DataGridCheckBoxColumnを利用しています。
DataGridCheckBoxColumnのXAMLでの記述は以下のようになります。

<DataGridCheckBoxColumn
    Binding="{Binding Path=IsRegular}" CanUserReorder="True"
    CanUserResize="True"
    CanUserSort="True" Header="正会員" Width="50" />

bool値をバインドする以外は、DataGridTextColumnと同じですね。

次に、DataGridTemplateColumnです。

<DataGridTemplateColumn
    CanUserReorder="True" CanUserResize="True" CanUserSort="True"
    Width="Auto"
    CellTemplate="{StaticResource DateTemplate}" Header="生年月日"
    CellEditingTemplate="{StaticResource DateEditTemplate}" />

CellTemplatetとCellEditingTemplateという2つのプロパティで、表示のときと編集のときの2つのテンプレートを指定しています。
この2つのテンプレート(DateTemplateとDateEditTemplate)は、Windows.Resourcesで以下のように定義しています。

<Window.Resources>
    <DataTemplate x:Key="DateTemplate">
        <StackPanel Orientation="Horizontal" Margin="5,0" VerticalAlignment="Center">
            <TextBlock Text="{Binding Birthday, StringFormat=yyyy年MM月dd日}" />
        </StackPanel>
    </DataTemplate>
    <DataTemplate x:Key="DateEditTemplate">
        <DatePicker SelectedDateFormat="Short" SelectedDate="{Binding Birthday, Mode=TwoWay}" />
    </DataTemplate>
</Window.Resources>

このように定義することで、日付編集時にDatePickerを使うことが可能になります。

最後にXAML全体を示します。

<Window x:Class="DataGrid3Sample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:DataGrid3Sample"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="DateTemplate">
            <StackPanel Orientation="Horizontal" Margin="5,0" VerticalAlignment="Center">
                <TextBlock Text="{Binding Birthday, StringFormat=yyyy年MM月dd日}" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="DateEditTemplate">
            <DatePicker SelectedDateFormat="Short" SelectedDate="{Binding Birthday, Mode=TwoWay}" />
        </DataTemplate>
    </Window.Resources>
    <Window.DataContext>
        <my:UserList x:Name="userList"/>
    </Window.DataContext>
    <Grid x:Name="LayoutRoot" Background="White">
        <DataGrid AutoGenerateColumns="False" Margin="10" Name="dataGrid1"
                  ItemsSource="{Binding Path=Users}">
            <DataGrid.Columns>
                <DataGridTextColumn
                    Binding="{Binding Path=Nickname}" CanUserReorder="True"
                    CanUserResize="True"
                    CanUserSort="True" Header="ユーザ名" Width="80" />
                <DataGridTextColumn
                    Binding="{Binding Path=Name}" CanUserReorder="True"
                    CanUserResize="True"
                    CanUserSort="True" Header="氏名" Width="100" />
                <DataGridCheckBoxColumn
                    Binding="{Binding Path=IsRegular}" CanUserReorder="True"
                    CanUserResize="True"
                    CanUserSort="True" Header="正会員" Width="50" />
                <DataGridTemplateColumn
                    CanUserReorder="True" CanUserResize="True" CanUserSort="True"
                    Width="Auto"
                    CellTemplate="{StaticResource DateTemplate}" Header="生年月日"
                    CellEditingTemplate="{StaticResource DateEditTemplate}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

このサンプルでは、コードビハインドへのC#での追加コーディングはありません。



WPFサンプル・目次
  
Posted by gushwell at 22:30Comments(0)TrackBack(0)

2013年09月25日

WPFサンプル:プロパティ値を変更してDataGridの見た目と動作を変更する

   このエントリーをはてなブックマークに追加 Clip to Evernote
DataGridには、さまざまなプロパティが用意されていて、これらのプロパティ値を変更することで、DataGridの見た目や動作を簡単にカスタマイズすることができます。
前回「DataGridのサンプルプログラム(1)」で示したXAMLをベースに、いくつかのプロパティの値を変更してみました。

まずは、スクリーンショットを。

DataGrid21

XAMLは次のとおりです。

<Window x:Class="DataGrid2Sample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:DataGrid2Sample"
        Title="MainWindow" Height="350" Width="420">
    <Window.DataContext>
        <my:UserList x:Name="userList"/>
    </Window.DataContext>
    <Grid x:Name="LayoutRoot" Background="White" >
        <!-- プロパティ値変更だけでできる簡易カスタマイズ -->
        <DataGrid AutoGenerateColumns="False" Margin="5,5,5,8" Name="dataGrid2"
                      ItemsSource="{Binding Path=Users}"
                      GridLinesVisibility="Vertical"
                      VerticalGridLinesBrush="LightSteelBlue"
                      HeadersVisibility="All" RowHeaderWidth="20"
                      HorizontalScrollBarVisibility="Hidden"
                      VerticalScrollBarVisibility="Visible"
                      RowBackground="LightYellow"
                      AlternatingRowBackground="#f0f0f0"
                      SelectionMode="Single"
                      SelectionUnit="CellOrRowHeader"
                      Width="383" RowHeight="26" IsReadOnly="True">
            <DataGrid.Columns>
                <DataGridTextColumn
                        Binding="{Binding Path=Nickname}" CanUserReorder="True"
                        CanUserResize="True"
                        CanUserSort="True" Header="ユーザ名" Width="70" />
                <DataGridTextColumn
                        Binding="{Binding Path=Name}" CanUserReorder="True"
                        CanUserResize="True"
                        CanUserSort="True" Header="氏名" Width="90" />
                <DataGridTextColumn
                        Binding="{Binding Path=Birthplace}" CanUserReorder="True"
                        CanUserResize="True"
                        CanUserSort="True" Header="出身地" Width="60" />
                <DataGridTextColumn
                        Binding="{Binding Path=Age}" CanUserReorder="True"
                        CanUserResize="True"
                        CanUserSort="True" Header="年齢" Width="45" />
                <DataGridTextColumn
                        Binding="{Binding Path=Birthday, StringFormat=\{0:yyyy/MM/dd\}}"
                        CanUserReorder="True" CanUserResize="True" CanUserSort="True"
                        Header="生年月日" Width="Auto" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

以下、このサンプルで利用しているプロパティについての簡単な説明です。
  • GridLinesVisibility
  • どのグリッド線を表示するかを指定します。指定できるのは、All, Horizontal, Verical, None の4つです。 Allが規定値です。
  • VerticalGridLinesBrush
  • 垂直グリッド線の描画に使用するブラシを指定します。同様に、HorizontalGridLinesBrush プロパティで、水平グリッド線を指定できます。
  • HeadersVisibility
  • 行と列ヘッダーの表示有無を指定します。指定できるのは、All, Column, Row, None の4つです。 既定値は Allです。
  • RowHeaderWidth
  • 行見出しの列の幅を設定します。
  • HorizontalScrollBarVisibility, VerticalScrollBarVisibility
  • 水平/垂直スクロール バーの表示方法を指定します。Auto Disabled Hidden Visibleの4つから指定します。既定値は Autoです。Disabled Hiddenの違いが分かっていません (T T)
  • RowBackground
  • 行の背景のブラシを指定します。
  • AlternatingRowBackground
  • 1行おきの背景色のブラシを指定します。 なお、AlternationCount のプロパティを3に設定すれば、3行おきに背景色を変えられます。
  • SelectionMode
  • Singleの場合、ユーザーは一つの項目を選択できます。 Extendedの場合、複数の項目を選択できます。 規定値は Extendedです。
  • SelectionUnit
  • 行を選択できるのか、セルが選択できるのか、またはその両方なのかを指定します。 FullRow, Cell, CellOrRowHeader のいづれかを指定します。 規定値は FullRowです。
  • IsReadOnly
  • 読み取り専用の場合trueを、編集可ならな falseを指定します。 既定値は falseです。

いちおう、C#のコードも載せておきますね。
ちなみに、コードビハインドへの追加コードはありません。

using System;
using System.Collections.ObjectModel;
namespace DataGrid2Sample {
    public class User {
        public string Nickname { get; set; }
        public string Name { get; set; }
        public DateTime Birthday { get; set; }
        public string Birthplace { get; set; }
        public int Age {
            get {
                DateTime today = DateTime.Today;
                int age = today.Year - Birthday.Year;
                if (today.Month < Birthday.Month)
                    age--;
                else if (today.Month == Birthday.Month && today.Day < Birthday.Day)
                    age--;
                return age;
            }
        }
    }
    public class UserList {
        public ObservableCollection<User> Users { get; set; }
        public UserList() {
            Users = new ObservableCollection<User> {
                new User { Name="芥川 隆乃輔", Nickname="ryu",
                           Birthday= new DateTime(1950,3,6), Birthplace="東京"},
                new User { Name="名都目 漱石", Nickname="natsu",
                           Birthday= new DateTime(1982,11,13), Birthplace="神奈川"},
                new User { Name="宮沢 健治", Nickname="miya",
                           Birthday= new DateTime(1964,7,24), Birthplace="宮城"},
                new User { Name="島咲 藤村", Nickname="saki",
                           Birthday= new DateTime(1971,2,5), Birthplace="長野"},
                new User { Name="紀久地 寛", Nickname="kan",
                           Birthday= new DateTime(1963,12,15), Birthplace="長崎"},
            };
        }
    }
}



WPFサンプル・目次
  
Posted by gushwell at 22:00Comments(0)TrackBack(0)

2013年09月23日

WPFサンプル:DataGrid にコレクションをバインドする

   このエントリーをはてなブックマークに追加 Clip to Evernote
まずはサンプルプログラムの実行例です。

DataGrid11

DataGrid12

それでは、上記サンプルのDataGridに表示されているデータを保持するクラスの定義をします。
最初に、一人分のデータを保持する User クラスです。

public class User {
    public string Nickname { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
    public string Birthplace { get; set; }
    public int Age {
        get {
            DateTime today = DateTime.Today;
            int age = today.Year - Birthday.Year;
            if (today.Month < Birthday.Month)
                age--;
            else if (today.Month == Birthday.Month && today.Day < Birthday.Day)
                age--;
            return age;
        }
    }
}

次に、このUserを複数保持するクラス UserList を定義します。
UserListクラスは、メンバーとして ObservableCollection<User> 型の Users プロパティを持っていて、 ここで複数のUserを管理しています。

public class UserList {
    public ObservableCollection <User> Users { get; set ; }
    public UserList() {
        Users = new ObservableCollection <User> {
            new User { Name="芥川 隆乃輔", Nickname="ryu" ,
                       Birthday= new DateTime (1950,3,6), Birthplace="東京"},
            new User { Name="名都目 漱石", Nickname="natsu" ,
                       Birthday= new DateTime (1982,11,13), Birthplace="神奈川"},
            new User { Name="宮沢 健治", Nickname="miya" ,
                       Birthday= new DateTime (1964,7,24), Birthplace="宮城"},
            new User { Name="島咲 藤村", Nickname="saki" ,
                       Birthday= new DateTime (1971,2,5), Birthplace="長野"},
            new User { Name="紀久地 寛", Nickname="kan" ,
                       Birthday= new DateTime (1963,12,15), Birthplace="長崎"},
        };
    }
}


サンプルプログラムの便宜上、コンストラクタで固定的にUsersプロパティを初期化しています。
では、XAMLです。

<Window x:Class="DataGrid1Sample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:DataGrid1Sample"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <my:UserList x:Name="userList"/>
    </Window.DataContext>
    <Grid x:Name="LayoutRoot" Background="White">
        <DataGrid AutoGenerateColumns="False" Margin="10" Name="dataGrid1"
                  ItemsSource="{Binding Path=Users}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Path=Nickname}"
                    CanUserReorder="True" CanUserResize="True"
                    CanUserSort="True" Header="ユーザ名" Width="80" />
                <DataGridTextColumn Binding="{Binding Path=Name}"
                    CanUserReorder="True" CanUserResize="True"
                    CanUserSort="True" Header="氏名" Width="100" />
                <DataGridTextColumn Binding="{Binding Path=Birthplace}"
                    CanUserReorder="True" CanUserResize="True"
                    CanUserSort="True" Header="出身地" Width="80" />
                <DataGridTextColumn Binding="{Binding Path=Age}"
                    CanUserReorder="True" CanUserResize="True"
                    CanUserSort="True" Header="年齢" Width="50" />
                <DataGridTextColumn Binding="{Binding Path=Birthday,
                        StringFormat=\{0:yyyy/MM/dd\}}"
                    CanUserReorder="True" CanUserResize="True"
                    CanUserSort="True" Header="生年月日" Width="Auto" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

DataGridでは、UserListオブジェクトの Users プロパティをバインドしています。
DataGridTextColumn でカラム定義をしていますが、そこで Userオブジェクトのどのプロパティと バインドするのかを指定しています。

DataGridとDataGridTextColumnのプロパティについて間単に触れておくと、AutoGenerateColumnsは、カラムの自動生成有無を指定します。ここでは、falseを指定しているので、 Columns プロパティで、カラムを定義することになります。
DataGridTextColumnの CanUserReorder 列ヘッダーをドラッグして列の表示位置を変更できるかどうかを指定するものです。
CanUserResizeは、マウスを使用して列幅を調整できるかどうかを指定します。
CanUserSortは、列ヘッダーをクリックして列を並べ替えることができるかどうか指定します。

UserListのインスタンス生成は、XAML側で行っています(UserListを生成すると同時に、DataContextにインスタンスを設定しています)ので、C#のコードビハインドでは新たな記述は特にありません。



WPFサンプル・目次
  
Posted by gushwell at 22:00Comments(4)TrackBack(0)

2013年09月15日

WPFサンプル:階層構造をもったデータをTreeViewにバインドする

   このエントリーをはてなブックマークに追加 Clip to Evernote
まずはサンプルプログラムの実行時のスクリーンショットを。

treeview

TreeViewにデータを表示するということは、そのデータが階層構造を持っているということですから、 TreeViewのバインドするデータは、階層構造をもったクラスということになります。ここでは、Categoryというクラスを定義することとします。

public class Category : ObservableCollection<Category> {
    public Category() {
    }
    public Category(string name) {
        Name = name;
    }
    public string Name { get; set; }
 
    public Category Children { get; set; }
}

Categoryクラスには、Nameプロパティと、Childrenプロパティがあります。Nameはカテゴリの名前を、Childrenは、そのサブカテゴリを表します。
なお、Category自身を、ObservableCollection<Category> を継承したクラスとしており 複数のCategoryを要素として保持できるコレクションクラスとなっています。
そして、Childrenプロパティが自分自身のクラスであるCategory型なので、 階層的にデータを保持できるということです。

ここでは、さらに、OSAndBrowserというクラスを定義しています。
このクラスには、Categoryクラスをメンバーとして保持し、 このCategoryオブジェクトの初期化を行っています。

public class OSAndBrowser {
    public Category Categories { get; set; }
    public OSAndBrowser() {
        Categories = new Category() {
            new Category("OS") {
                Children = new Category {
                    new Category("Windows") {
                        Children = new Category {
                            new Category("Windows 8"),
                            new Category("Windows 7"),
                            new Category("Windows Vista"),
                            new Category("Windows XP"),
                        }
                    },
                    new Category("Mac OS X"),
                    new Category("Linux")
                }
            },
            new Category("ブラウザ") {
                Children = new Category {
                    new Category("Internet Explorer") {
                        Children = new Category {
                            new Category("IE 11.0"),
                            new Category("IE 10.0"),
                            new Category("IE 9.0"),
                            new Category("IE 8.0"),
                            new Category("IE 7.0"),
                        }
                    },
                    new Category("Firefox"),
                    new Category("Chrome"),
                    new Category("Opera"),
                    new Category("Safari"),
                }
            }
        };
    }
}

初期化のコードを見れば、Categoryがデータを階層的に保持している様子が 分かると思います。

では、XAMLです。

<Window x:Class="TreeViewSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:TreeViewSample"
        Title="MainWindow" Height="400" Width="500" >
    <Window.Resources>
        <my:OSAndBrowser x:Key="OSAndBrowser" />
        <HierarchicalDataTemplate x:Key="treeViewTemplate"
                                  ItemsSource="{Binding Path=Children}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Window.DataContext>
        <Binding Mode="OneWay" Source="{StaticResource OSAndBrowser}"/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5*" />
            <ColumnDefinition Width="4*" />
        </Grid.ColumnDefinitions>
       
        <TreeView HorizontalAlignment="Stretch" Margin="7"  Name="treeView1"
                  VerticalAlignment="Stretch"
                  ItemTemplate="{StaticResource treeViewTemplate}"
                  ItemsSource="{Binding Categories}" />
        <TextBlock Grid.Column="1"  Height="23"  HorizontalAlignment="Left"
                   Margin="7" Name="textBlock1"  VerticalAlignment="Top"
                   Text="{Binding SelectedItem.Name, ElementName=treeView1,
                                  Mode=OneWay}" />
    </Grid>
</Window>

WindowのDataContextには、Window.Resourcesで定義した OSAndBrowserオブジェクトを設定しています。
TreeViewのItemsSourceプロパティには、OSAndBrowserオブジェクトのCategoriesプロパティをバインドしています。
これだけでは、TreeViewにどの要素を表示したらよいのかが分かりませんので、 ItemTemplate と HierarchicalDataTemplateを使い、表示する項目がNameプロパティであること、 階層構造として、Childrenプロパティを使うことを指定しています。
ウィンドウ右側のTextBlockでは、TreeViewで選択された項目の名前をバインドしています。



WPFサンプル・目次
  
Posted by gushwell at 22:30Comments(3)TrackBack(1)

2013年09月12日

WPFサンプル:ListBoxで複数項目を選択可にし選択状態をモデル側に通知する

   このエントリーをはてなブックマークに追加 Clip to Evernote
ListBoxで複数項目を選択可にし選択状態をモデル側に通知する次のようなアプリケーションの作成を考えてみましょう。

2つのListBoxを用意し、左側には、いくつかの項目が表示されています。
この項目を選択(複数選択可)すると、選択された項目が、右側のListBoxに表示されるようにします。
すべての項目が選択されたら、左側のListBoxには新たな項目をひとつ追加します(変な要件ですが)

MultiSelectListBox1

MultiSelectListBox2

ListBoxには、SelectedItemsというプロパティがありますから、これをバインドすれば、比較的簡単に実現できますが、ここでは、モデル側でどの項目が選択されたのかを把握できるようにし、このモデル側のデータを右側のListBoxにバインドしたいと思います。

まずは、モデル側のクラスの定義です。
ListBoxに表示される項目である、MyDataクラスを定義します。

public class MyData {
    public string Name { get; set; }

    public bool? IsSelected { get; set; }
}

Nameプロパティが、ListBoxに表示される項目で、 IsSelectedプロパティが選択されているかどうかを把握するプロパティです。
このクラスについては、INotifyPropertyChanged は、実装しません。
なくても、上記要求は満たせます。

次に、このMyDateのコレクションクラスを定義します。

public class MyDataList : ObservableCollection<MyData> {
    public MyDataList() {
        AddNewItem();
        AddNewItem();
        AddNewItem();
        AddNewItem();
    }

    private int lastYear = 2010;

    public MyData AddNewItem() {
        var data = new MyData { Name = string.Format("{0}年", lastYear) };
        this.Add(data);
        lastYear++;
        return data;
    }

    public List<MyData> SelectedItems { get; private set; }

    public void UpdateSelectedItems() {
        var q = Items.Where(x => x.IsSelected.HasValue && (x.IsSelected.Value == true));
        SelectedItems = new List<MyData>(q);
        this.OnPropertyChanged(new PropertyChangedEventArgs("SelectedItems"));
    }
}

こちらは、ObservableCollection を継承したクラスとします、 こうすることで、コレクションへの追加、削除の変更が通知されるようになります。

SelectedItemsプロパティは、選択されている項目一覧が格納されているコレクションです。

UpdateSelectedItemsメソッドで、SelectedItemsを最新の状態に更新し、継承元で定義されている OnPropertyChangedメソッドで、SelectedItemsプロパティが更新されたことを通知します。

これで、モデル側の準備ができましたので、XAMLを定義します。

<Window x:Class="MultiSelectionListBoxSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:MultiSelectionListBoxSample"
        Title="MainWindow" Height="220" Width="300">
    <Window.DataContext>
        <my:MyDataList />
    </Window.DataContext>
    <Window.Resources>
        <Style x:Key="listBoxItemStyle" TargetType="{x:Type ListBoxItem}">
            <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <ListBox Height="140" HorizontalAlignment="Center"
                 Name="listBox" VerticalAlignment="Center" Width="120"
                 SelectionMode="Multiple"
                 ItemsSource="{Binding}"  DisplayMemberPath="Name"
                 ItemContainerStyle="{StaticResource listBoxItemStyle}"
                 SelectionChanged="listBox1_SelectionChanged" />
        <ListBox Grid.Column="1"
                 Height="140" HorizontalAlignment="Center"
                 Name="listBox2" VerticalAlignment="Center" Width="116"
                 ItemsSource="{Binding SelectedItems}" DisplayMemberPath="Name"  />
    </Grid>
</Window>

左側のListBoxでは、ItemContainerStyleを設定し、その中で、ListBoxの各項目を表すクラスであるListBoxItemの IsSelectedプロパティと、モデル側の MyData の IsSelectedプロパティをバインドさせています。
こうすることで、選択状態が変わるたびに、MyDataオブジェクトのIsSelectedプロパティも 同期することになります。

右側のListBoxは、MyDataListオブジェクトのSelectedItemsプロパティとバインドさせています。

この状態で、プログラムを実行した場合、左側のListBoxの選択状態を変更しても、 右側のListBoxは何も変化しません。
MyDataListのUpdateSelectedItemsメソッドがどこでも呼び出されていませんので、 SelectedItemsプロパティの変更通知がされないからです。

この部分は、C#のコードビハインド側で記述することにします。

SelectionChangedイベントハンドラで、UpdateSelectedItems を呼び出すようにします。
また、最後の要件である「すべての項目が選択されたら、左側のListBoxには新たな項目をひとつ追加します」 のコードもここに記述します。

以下、C#のコードです。

using System.Windows;
using System.Windows.Controls;

namespace MultiSelectionListBoxSample {

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

        private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            var myList = (this.DataContext as MyDataList);
            myList.UpdateSelectedItems();
            if (myList.SelectedItems.Count == myList.Count) {
                myList.AddNewItem();
            }
        }
    }
}    



WPFサンプル・目次
  
Posted by gushwell at 23:00Comments(0)TrackBack(0)

2013年09月05日

WPFサンプル:ObjectDataProviderを使いメソッドをバインドする (3)

   このエントリーをはてなブックマークに追加 Clip to Evernote
ObjectDataProviderの例をもうひとつお見せします。
今回のサンプルは、以下のような簡易デジタル時計です。

MethodBinding3


まずは、ObjectDataProviderにデータを供給するMyClockクラス。

public class MyClock {
    private System.Timers.Timer _timer = new System.Timers.Timer();
    public  ObjectDataProvider Provider { get; set; }
    public MyClock() {
        _timer.Interval = 1000;
        _timer.Elapsed += _timer_Elapsed;
        _timer.Enabled = true;
    }
    private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
        Provider.Refresh();
    }
    public string NowTime() {
        return DateTime.Now.ToLongTimeString();
    }
}

NowTimeが現在の時刻を返すメソッドです。このメソッドをUI側のTextBlockとバインドさせるのですが、 時刻の更新をObjectDataProviderに知らせる必要があります。
そのため、System.Timerを利用し、1秒おきに、ObjectDataProviderのRefreshメソッドを呼び出しています。
なお、このMyClockオブジェクトのProviderプロパティ(ObjectDataProvider型)は、以下のように、C#のコードビハインドで設定しています。

public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
        MyClock myClock = this.Resources["myClock"] as MyClock;
        myClock.Provider = this.Resources["NowTime"] as ObjectDataProvider;
    }
}

C#のコードビハインドで記述するのはこれだけです。

XAMLを示します。

<Window x:Class="MethodBindingSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:system="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:MethodBindingSample"
        Title="MainWindow" Height="100" Width="200">
    <Window.Resources>
        <local:MyClock x:Key="myClock" />
        <ObjectDataProvider MethodName="NowTime" x:Key="NowTime"
                            ObjectInstance="{StaticResource myClock}"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource NowTime}}">
        <TextBlock Height="28" Name="textBlock2" FontSize="18"
                   HorizontalAlignment="Center"
                   Text="{Binding Source={StaticResource NowTime}}" />
    </Grid>
</Window>

今回は、ObjectDataProvider の ObjectTypeプロパティではなく、ObjectInstanceプロパティを使ってみました。

WPFサンプル・目次
  
Posted by gushwell at 22:30Comments(3)TrackBack(0)

2013年09月03日

WPFサンプル:ObjectDataProviderを使いメソッドをバインドする (2)

   このエントリーをはてなブックマークに追加 Clip to Evernote
ObjectDataProviderを使いメソッドをバインドする (1)では引数のないメソッドをバインドしました。今回は、引数ありのメソッドをバインドしてみます。
作成したメソッドを示します。

public class ToUpperConverter {
    public string ToUpper(string s) {
        return s.ToUpper();
    }
}

String.ToUpperメソッドをラップしただけの単純なメソッドです。
このメソッドに、TextBoxに入力された文字列を引数として渡し、大文字化された文字列をTextBlockに表示してみます。

<Window x:Class="MethodBindingSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:system="clr-namespace:System;assembly=mscorlib"
         xmlns:local="clr-namespace:MethodBindingSample"
        Title="MainWindow" Height="150" Width="325">
    <Window.Resources>
        <ObjectDataProvider ObjectType="{x:Type local:ToUpperConverter}"
                      MethodName="ToUpper" x:Key="toUpper">
            <ObjectDataProvider.MethodParameters>
                <x:Static Member="system:String.Empty" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <StackPanel>
        <TextBox Height="24" Name="textBox1" >
            <TextBox.Text>
                <Binding Source="{StaticResource toUpper}"
                     Path="MethodParameters[0]"
                     Mode="OneWayToSource"
                     BindsDirectlyToSource="true"
                     UpdateSourceTrigger="PropertyChanged" />
            </TextBox.Text>
        </TextBox>
        <TextBlock Height="28" Name="textBlock1" Margin="5"
                   Text="{Binding Source={StaticResource toUpper}}" />
    </StackPanel>
</Window>

ObjectDataProviderでは、MethodParameters プロパティで、String型の引数があることを指定します。
そして、TextBoxの入力値(Textプロパティの値)をメソッドに渡しているのが、TeXtBox.Textプロパティのバインドの指定です。
Sourceには、ObjectDataProvider を、Pathで、0番目の引数であることを指定しています。
UpdateSourceTriggerでは、PropertyChangedとすることで、キーボードから文字が入力されるたびに、メソッドが呼び出されるようにしています。
TextBlockでは、

Text="{Binding Source={StaticResource toUpper}}"

と記述することで、toUpperと名付けられたObjectDataProviderをバインドし、そのメソッドの戻り値をTextプロパティにバインドしています。
C#のコードビハインドには何もコードは追加していません。

実行時のスクリーンショットです。

MethodBinding2

WPFサンプル・目次
  
Posted by gushwell at 23:00Comments(0)TrackBack(0)