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月09日

WPFサンプル:未処理例外に対応する

   このエントリーをはてなブックマークに追加 Clip to Evernote
アプリケーション実行中に例外が発生し、どこでもハンドルされなかった場合、アプリケーソンは異常終了してしまいます。僕が試した環境では、エラーダイアログも何もで出ずにプログラムが終わってしまいました。

WPFでは、ハンドルされない例外を検出するためのDispatcherUnhandledException イベントを利用することで、既定の動作を変更し、ユーザーにわかりやすい情報を表示させることが可能になります。

DispatcherUnhandledException イベントを利用するには、App.XAMLで、DispatcherUnhandledExceptionイベントハンドラを以下のように設定します。

<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"
             DispatcherUnhandledException="Application_DispatcherUnhandledException">
    <Application.Resources>
        
    </Application.Resources>
</Application>

App.Xaml.csに、このイベントハンドラを記述します。

using System.Windows;
using System.Windows.Threading;
namespace UnhandledExceptionSample {
    public partial class App : Application {
        public void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) {
            string message = string.Format(
                "大変申し訳けありません。システムエラーが発生しました。\n ({0} {1})",
                e.Exception.GetType(), e.Exception.Message);
            MessageBox.Show(message);
            e.Handled = true;
        }
    }
}

DispatcherUnhandledExceptionイベントハンドラ―には、第2引数に、未処理の例外情報を含むDispatcherUnhandledExceptionEventArgsが渡されます。このクラスのプロパティ Exceptionを参照することで未処理の例外情報を得ることができます。

未処理の例外を処理した場合には、Handledプロパティにtrueを設定します。こうすることで、処理済みであることをApplicationオブジェクトに知らせ、既定の動作を抑制することができます。

今回は、DispatcherUnhandledException イベントの設定有無でどうアプリケーションの動作が変わるかを確認するため、MainWindow側のコードを以下のように記述しました。

<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="160" Width="225">
    <Grid>
        <Button Content="例外発生" HorizontalAlignment="Center"
                Margin="0,27,0,0" VerticalAlignment="Top" Width="105"
                Click="Button_Click_1"/>
        <Button Content="例外処理解除" HorizontalAlignment="Center"
                Margin="0,72,0,0" VerticalAlignment="Top" Width="105"
                Click="Button_Click_2"/>
    </Grid>
</Window>


using System.Windows;
namespace UnhandledExceptionSample {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
        private void Button_Click_1(object sender, RoutedEventArgs e) {
            throw new System.NotImplementedException("まだ実装していません");
        }
        private void Button_Click_2(object sender, RoutedEventArgs e) {
            App app = Application.Current as App;
            app.DispatcherUnhandledException -= app.Application_DispatcherUnhandledException;
        }
    }
}

実行結果を示します。

[例外発生]ボタンを押したときの画面
ExceptionProc



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