2013年11月05日

WPFサンプル・目次

   このエントリーをはてなブックマークに追加 Clip to Evernote
昨年(2012年)から掲載しているWPFのサンプルコードですが、随分と数が増えてきたので、目次を作成しました。
※ この目次の順序は、記事の掲載順とは若干変更しています。

コントロール リソースの利用 データ・バインディング イベント コマンド 2D Graphics スタイル トリガー DataTemplate等によるコントロールのカスタマイズ アニメーション ドラッグ・ドロップ アプリケーション・ファイル その他   

Posted by gushwell at 21:30Comments(4)TrackBack(0)

2013年10月31日

WPFサンプル:VisualStateManagerでコントロールの状態ごとの外観をカスタマイズする

   このエントリーをはてなブックマークに追加 Clip to Evernote
VisualStateManagerを使うと、コントロールの状態ごとに外観を変化させることが可能です。
VisualStateManagerを使って、Buttonの外観を変えてみました。

ここでは、Window.ResourcesにControlTemplateを定義し、その中で、isualStateManagerを使って、Buttonの外観を変化させています。
Buttonにはすでに、"Normal" "MouseOver" といった状態が定められているので、その状態ごとにどういったAnimationを実行するかを定義することができます。

<Window x:Class="VisualStateManagerButton.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style TargetType="Button" >
            <Setter Property="Background" Value="#115E8B" />
            <Setter Property="Foreground" Value="White" />
        </Style>
        <ControlTemplate  x:Key="ButtonTemplate" TargetType="Button">
           
            <Grid x:Name="MyButton" Background="{TemplateBinding Background}" Margin="1">
                <ContentPresenter
                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                       
                        Margin="4,5,4,4" />
                <VisualStateManager.VisualStateGroups>
                   
                    <VisualStateGroup Name="CommonStates">
                        <VisualState Name="Normal" />
                        <VisualState Name="MouseOver">
                            <Storyboard>
                                <ColorAnimation Storyboard.TargetName="MyButton"
                                        Storyboard.TargetProperty="Background.(SolidColorBrush.Color)"                       
                                        Duration="00:00:00.1"
                                        To="#1570A6" />
                                <ThicknessAnimation Storyboard.TargetName="MyButton"
                                        Storyboard.TargetProperty="Margin"
                                        Duration="00:00:00.1"
                                        To="0,0,0,0" />
                            </Storyboard>                           
                        </VisualState>
                        <VisualState Name="Pressed">
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="MyButton"
                                        Storyboard.TargetProperty="Opacity"
                                        Duration="00:00:00.1"
                                        To="0.6"/>
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
            </Grid>
        </ControlTemplate>
    </Window.Resources>
    <Grid>
        <Button Width="120" Height="40"
                Template="{StaticResource ButtonTemplate}"
                Content="Click Me!"  />
    </Grid>
</Window>

C#のコード側では何も記述する必要はありません。

以下、実行結果です。


通常の状態
VisualState1

マウスオーバーの状態
VisualState2

マウスをプレスした状態
VisualState3



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

2013年10月20日

WPFサンプル:UIスレッド以外で発生した未処理例外には対応する

   このエントリーをはてなブックマークに追加 Clip to Evernote
WPFサンプル:未処理例外に対応する」 では、UIスレッド内で発生した未処理例外に対応するサンプルを掲載しましたが、UIスレッド以外で発生した未処理例外には対応できません。
UIスレッド以外で発生した未処理例外には対応するには、AppDomain.CurrentDomain.UnhandledException イベントを利用します。
そのサンプルを以下に示します。

UnhandledExceptionSample1

UnhandledExceptionSample2


UnhandledExceptionイベントハンドラの設定は、以下のように、App.xaml.csでやるのが適当かと思います。

using System;
using System.Windows;
namespace UnhandledExceptionSample {
    public partial class App : Application {
        private void Application_Startup(object sender, StartupEventArgs e) {
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }
        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
            Exception ex = e.ExceptionObject as Exception;
            MessageBox.Show(ex.Message, "例外発生(UI スレッド外)",
                                  MessageBoxButton.OK, MessageBoxImage.Error);
            this.Shutdown();
        }
        private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) {
            MessageBox.Show(e.Exception.Message, "例外発生",
                                  MessageBoxButton.OK, MessageBoxImage.Error);
            e.Handled = true;
        }
    }
}


コードを見ていただければわかると思いますが、UIっスレッド以外で発生した未処理例外の場合は、 Shutdownメソッドを呼び出して、アプリケーションを終了させています。
Shutdownメソッドの行をコメントにしても、アプリケーションは終了してしまいます。
もし、アプリケーションを終了させたくない場合は、スレッド内で発生した例外はそのスレッド内で try-catchを使い、例外処理を記述する必要があります。
App.xamlは、「WPFサンプル:未処理例外に対応する」のものと変更ありません。

<Application x:Class="UnhandledExceptionSample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml"
             Startup="Application_Startup"
             DispatcherUnhandledException="Application_DispatcherUnhandledException"
             >
    <Application.Resources>
        
    </Application.Resources>
</Application>

以下、MainWindow.xaml と MainWindow.xaml.cs です。

<Window x:Class="UnhandledExceptionSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="120" Width="260" >
    <StackPanel >
        <Button Content="UIスレッドで例外発生"  Margin ="3"
                Width="186" Click="Button_Click1"/>
        <Button Content="UIスレッド外で例外発生"  Margin="3"
                Width="186" Click="Button_Click2" />
    </StackPanel>
</Window>


using System.Threading;
using System.Windows;
namespace UnhandledExceptionSample {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
        private void Button_Click1(object sender, RoutedEventArgs e) {
            throw new System.NotImplementedException();
        }
        private void Button_Click2(object sender, RoutedEventArgs e) {
            Thread th = new Thread(DoWork);
            th.Start();
        }
        private void DoWork(object obj) {
            throw new System.NotImplementedException();
        }
    }
}

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

2013年10月14日

WPFサンプル:NavigationServiceを使ったページ遷移

   このエントリーをはてなブックマークに追加 Clip to Evernote
ウィザード画面のように、次へ、前へボタンがあるようなプログラムを WPFのナビゲーション機能を使って実装したサンプルを掲載します。

どんなプログラムなのかイメージがわくように、まずは実行時のスクリーンショットをお見せします。

Navi1

Navi2

Navi3

以下、作成手順です。

1. ウィンドウの作成 WPFアプリケーションプロジェクトを新規作成してできる MainWindowのXAMLを以下のように変更します。

<Window x:Class="PageNavigationSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="250" Width="325">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="32"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="32"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Background="#FF043957">
            <TextBlock Margin="10,0,0,0" VerticalAlignment="Center" Text="ページ遷移サンプル"
                       Foreground="White"/>
        </StackPanel>
        <Frame Grid.Row="1" NavigationUIVisibility="Hidden" Name="myFrame" />
        <StackPanel Orientation="Horizontal" Background="#FF043957" Grid.Row="2">
            <Button Name="prevButton" Content="Prev" HorizontalAlignment="Left"
                    Margin="10,0,0,0" Grid.Row="2" VerticalAlignment="Center"
                    Width="50" />
            <Button Name="nextButton" Content="Next" HorizontalAlignment="Left"
                    VerticalAlignment="Center" Width="50"  />
        </StackPanel>
    </Grid>
</Window>

■ デザイン時のスクリーンショット
Navi4

Gridを3つに分割し、真ん中にFrameを配置しています、ここに次の「ページの作成」で作成するページが表示されることになります。
下の段には、Prev, Nextボタンを配置します。


2. 各ページの作成 ソリューションエクスプローラでプロジェクトを右クリックし、[追加(D)]-[ページ(P)]で3つのページをプロジェクトに追加してください。[ウィンドウの追加]ではありませんので注意してください。それぞれのページを以下のようにデザインします。

Navi5

3つのページ共に、TextBlockを貼り付けただけの簡単なものです。

#ページ1
<Page x:Class="PageNavigationSample.Step01Page"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      mc:Ignorable="d"
      d:DesignHeight="186" d:DesignWidth="325"     Title="Step01Page1">
    <Grid>
        <TextBlock HorizontalAlignment="Left" Margin="20,20,0,0" TextWrapping="Wrap"
                   Text="ページ 1 " VerticalAlignment="Top"/>
    </Grid>
</Page>

#ページ2
<Page x:Class="PageNavigationSample.Step02Page"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      mc:Ignorable="d"
      d:DesignHeight="186" d:DesignWidth="300"
     Title="Step02Page1">
    <Grid>
        <TextBlock HorizontalAlignment="Left" Margin="40,30,0,0" TextWrapping="Wrap"
                   Text="ページ 2 " VerticalAlignment="Top"/>
    </Grid>
</Page>

#ページ3
<Page x:Class="PageNavigationSample.Step03Page"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      mc:Ignorable="d"
      d:DesignHeight="186" d:DesignWidth="300"
     Title="Step03Page1">
    <Grid>
        <TextBlock HorizontalAlignment="Left" Margin="60,40,0,0" TextWrapping="Wrap"
                   Text="ページ 3 " VerticalAlignment="Top"/>
    </Grid>
</Page>

3. NavigationServiceオブジェクトをフィールドに定義します。
public MainWindow() {
    InitializeComponent();
    _navi = this.myFrame.NavigationService;
}
private NavigationService _navi; 

NavigationServiceクラスは、戻る、進むなどのページナビゲーションを管理するためのクラスです。Frameは、ナビゲーションをサポートするコンテンツコントロールであり、 NavigationServiceプロパティがあります。
NavigationServiceオブジェクトを参照するのに、そのつど、Drameオブジェクトから取得するのは面倒なので、MainWindowのコンストラクタで、_navi フィールドに値をセットしています。

4. 初期ページを指定する
いろいろな方法が考えられますが、ここではもっとも手軽と思われる、コードビハインド側で指定することとします。Frameの Loadedイベントハンドラを以下のように記述します。
private List<Uri> _uriList = new List<Uri>() {
    new Uri("Step01Page.xaml",UriKind.Relative),
    new Uri("Step02Page.xaml",UriKind.Relative),
    new Uri("Step03Page.xaml",UriKind.Relative),
};
private void myFrame_Loaded(object sender, RoutedEventArgs e) {
    _navi.Navigate(_uriList[0]);
}

今回は、3つのページを管理するので、3ページのUriをListに記憶してお、その先頭のページを、NavigateメソッドでFrameに表示しています。

5. Prev, Next ボタンクリック時の動作を実装する
ボタンクリックのイベントハンドラを以下のように記述します。

private void prevButton_Click(object sender, RoutedEventArgs e) {
    if (_navi.CanGoBack)
        _navi.GoBack();
    else {
        int index = _uriList.FindIndex(p => p == _navi.CurrentSource) - 1;
        _navi.Navigate(_uriList[index]);
    }
}
private void nextButton_Click(object sender, RoutedEventArgs e) {
    if (_navi.CanGoForward)
        _navi.GoForward();
    else {
        int index = _uriList.FindIndex(p => p == _navi.CurrentSource) + 1;
        _navi.Navigate(_uriList[index]);
    }
}

一度訪れたページは、GoBack, GoForwordでページ遷移ができますので、Navigateメソッドではなく、GoBack, GoForword メソッドを使います。
まだ訪れていないページの場合は、Navigateメソッドでページ遷移をします。

6. ボタンのIsEnableを制御する

先頭ページの時は、Prevボタンを非活性化させボタンを押せないようにする必要があります。最後のページも同様ですね。
ここでは、FrameのNavigatedイベントで実装することとします。

private void myFrame_Navigated(object sender, NavigationEventArgs e) {
    int index = _uriList.IndexOf(_navi.CurrentSource);
    if (index <= 0)
        prevButton.IsEnabled = false;
    else
        prevButton.IsEnabled = true;
    if (index + 1 == _uriList.Count)
        nextButton.IsEnabled = false;
    else
        nextButton.IsEnabled = true;
}

以上で実装が完了です。


最後に、イベントハンドラの設定を追加したMainWindowのXAMLを掲載します。

<Window x:Class="PageNavigationSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="250" Width="325">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="32"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="32"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Background="#FF043957">
            <TextBlock Margin="10,0,0,0" VerticalAlignment="Center" Text="ページ遷移サンプル"
                       Foreground="White"/>
        </StackPanel>
        <Frame Grid.Row="1" NavigationUIVisibility="Hidden" Name="myFrame"
               Loaded="myFrame_Loaded" Navigated="myFrame_Navigated" />
        <StackPanel Orientation="Horizontal" Background="#FF043957" Grid.Row="2">
            <Button Name="prevButton" Content="Prev" HorizontalAlignment="Left"
                    Margin="10,0,0,0" Grid.Row="2" VerticalAlignment="Center"
                    Width="50" Click="prevButton_Click"/>
            <Button Name="nextButton" Content="Next" HorizontalAlignment="Left"
                    VerticalAlignment="Center" Width="50" Click="nextButton_Click" />
        </StackPanel>
    </Grid>
</Window>



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

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年10月02日

WPFサンプル:ControlTemplate で水平スクロールするListBox

   このエントリーをはてなブックマークに追加 Clip to Evernote
水平スクロールするListBoxのサンプルです。

HListBox

見てお分かりのようのリストボックスの要素として、画像を表示しています。
リストボックス内の画像が選択されると、ウィンドウ下部に選択された画像とその名前が表示されます。

まずは、C#側のコードから。
リストボックスに表示するオブジェクトのクラスMyImageクラスを定義します。

public class MyImage {
    public string Name { get; set; }
    public BitmapImage Bitmap { get; set; }
    public MyImage(string name, string uriStr) {
        Name = name;
        Bitmap = new BitmapImage(new Uri(uriStr, UriKind.RelativeOrAbsolute));
    }
}

MainWindowのコンストラクタでは、このMyImageを保持するListの初期化をして、このコレクションオブジェクトを DataContextの設定しています。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media.Imaging;
namespace HorizontalScrollListBoxSample {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            List<MyImage> myImages = new List<MyImage> {
                new MyImage("砂漠", "Images\\Desert.jpg"),
                new MyImage("アジサイ", "Images\\Hydrangeas.jpg"),
                new MyImage("くらげ", "Images\\Jellyfish.jpg"),
                new MyImage("コアラ", "Images\\Koala.jpg"),
                new MyImage("灯台", "Images\\Lighthouse.jpg"),
                new MyImage ("菊", "Images\\Chrysanthemum.jpg"),
                new MyImage("ペンギン", "Images\\Penguins.jpg"),
            };
            this.DataContext = myImages;
         }
    }
}

C#側のコードはこれだけです。

XAMLでは、まずは、Window.Resourcesに、ListBoxのStyleを定義します。
このStyleの中で、ControlTemplateを使い、ListBoxの見た目を変更しています。
一番内側に、StackPanelを配置し、Orientation="Horizontal" とすることで 要素が横に配置されるようにします。IsItemsHost="True" とすることで ListBoxの各要素が、このSTackPanelに配置されるようにします。
その外側に、ScrollViewer を配置し、スクロールバーで横スクロールするようにします。
さらのその外側に、Border を配置して枠が込みをします。

これで横スクロールするListBoxができたわけですが、まだ、MyImageオブジェクトを表示させることができません。
これをやっているのが、キー名に "PictOnly" をつけたDateTemplateです。

2つめのDateTemplate(キー名:"PictAndName")は、選択されたイメージを表示するもので、 ContentControl の ContentTemplate でBindingしています。

なお、ListBoxでは、ScrollViewer.CanContentScroll="False"を指定することで、 項目単位ではなく、ピクセル単位でスクロールするようにしています。

<Window x:Class="HorizontalScrollListBoxSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:HorizontalScrollListBoxSample"
        Title="MainWindow" Height="240" Width="360">
    <Window.Resources>
        <Style x:Key="myStyle" TargetType="ListBox">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBox">
                        <Border BorderThickness="1" BorderBrush="Gray">
                            <ScrollViewer HorizontalScrollBarVisibility="Auto"
                                   Margin="1,1,1,1"
                                   Background="{TemplateBinding Background}">
                                <StackPanel IsItemsHost="True"
                                            Orientation="Horizontal" />
                            </ScrollViewer>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <DataTemplate x:Key="PictOnly"   DataType="{x:Type local:MyImage}">
            <Image Height="70" Stretch="Uniform" Width="80"
                   Source="{Binding Bitmap}"/>
        </DataTemplate>
       
        <DataTemplate x:Key="PictAndName"  DataType="{x:Type local:MyImage}">
            <StackPanel Name="stackPanel1" Orientation="Horizontal">
                <Image Height="80" Stretch="Uniform" Source="{Binding Bitmap}"/>
                <Label Content="{Binding Name}" FontSize="14" VerticalAlignment="Center"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <StackPanel>
        <!-- ScrollViewer.CanContentScroll="False" で少しずつスクロール-->
        <ListBox Height="92" HorizontalAlignment="Stretch" Margin="5"
                 Name="listBox1" VerticalAlignment="Top" 
                 ScrollViewer.CanContentScroll="False"
                 Style="{StaticResource myStyle}"
                 ItemTemplate="{Binding Mode=OneWay, Source={StaticResource PictOnly}}"
                 ItemsSource="{Binding}" />
        <ContentControl Name="myImage" Height="80"
                 ContentTemplate="{Binding Mode=OneWay, Source={StaticResource PictAndName}}"
                 Content="{Binding SelectedItem, ElementName=listBox1}" />
    </StackPanel>
</Window>



WPFサンプル・目次
  
Posted by gushwell at 22:00Comments(0)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)