rainit2006 / C-Program

C# Program knowledge
0 stars 0 forks source link

XAML/WPF实现MVVM #16

Open rainit2006 opened 7 years ago

rainit2006 commented 7 years ago

With XAML, it is very easy to separate the behavior from the designer code. Hence, the XAML programmer and the designer can work in parallel.

Microsoft provides two important tools for XAML:

Templates A template describes the overall look and visual appearance of a control. In XAML, you can easily create your own templates when you want to customize the visual behavior and visual appearance of a control.

Animations and Transformations Animations and transformations inside the Windows Runtime can improve your XAML application by building interactivity and movement. You can easily integrate the interactive look and feel in your XAML application by using the animations from Windows Runtime animation library.

XAML - Data Binding Data binding is a mechanism in XAML applications that provides a simple and easy way for Windows Runtime Apps using partial classes to display and interact with data.

このPersonオブジェクトをソースに指定してTextBlockのTextプロパティにBindingするには以下のように記述します。 <TextBlock Text="{Binding Name, Source={StaticResource Person}}" />

Bindingの最初に指定するのがPathプロパティです。Pathにはプロパティ名を指定します。Bindingのソースは、指定されていない場合DataContextプロパティが自動的に使われるため以下のように書くことも出来ます。

<Window.DataContext>
    <local:Person Name="tanaka" Age="34" />
</Window.DataContext>
<Grid>
    <TextBlock Text="{Binding Name}" />
</Grid>

Data binding can be of four way types − 1.Default 何も指定しないとこれになる。TextBoxみたいな編集可能な奴はTwoWay的な動きをする。そうじゃないTextBlockみたいな編集不可なものはOneWay的な動きをする。 2.OneTime 最初の一回のみターゲットの値をソースからもってくる。最初の1回というのを厳密に言うと、アプリ起動時かDataContextの変更時。 3.OneWay ソースの変更をターゲットに通知する。それだけ。逆はしない。 比如GUI上有一个slider和一个textbox。 slider滑动时,textbox里可以显示出slider的value。

4.OneWayToSource OneWayの逆。ターゲットの変更をソースに通知する。それだけ。逆はしない。 5.TwoWay どっちの変更も通知しあう。 比如GUI上有一个slider和一个textbox。 slider滑动时,textbox里可以显示出slider的value; 同时slider的位置也可以随textbox里的值而改变。

https://www.youtube.com/watch?v=CniIPEFZ1Oo

rainit2006 commented 7 years ago

要了解MVVM的实现思路 WPF性能差的原因,目前改善了吗

rainit2006 commented 7 years ago

wpf性能问题要注意: https://msdn.microsoft.com/ja-jp/library/aa970683%28v=vs.110%29.aspx http://qiita.com/Koki_jp/items/f6ef5bbb2da9be44744c

wpf功能强大,但是如果处理不当,wpf会消耗大量的CPU资源。 WPF will still be doing the calculations for where things need to be drawn on the screen. You have not specified if this is a layout or render transform as this makes a performance difference. You need to be carefull

rainit2006 commented 7 years ago

MVVM WPFでアプリケーション開発する場合、MVVMパターンを利用しての開発が推奨されています。アプリケーションを Model, View, ViewModel の3つの層に分割して開発する手法です。

WPFではMVVMパターンを用いて開発するためにデータバインドとかいろいろ機能が用意されているのですが、それらを利用するためには幾つかのお約束があって、ViewModelではINotifyPropertyChangedとIDataErrorInfoという2つのインターフェイスを実装することが求めらます(別に必須ではないですが)。また、ViewからViewModelへの通知を受け取る場合、ICommandインターフェイスを介して行われるため、この実装も必要となります。

常见的实现思路:

定义一个ViewModelBase类:
public abstract class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
{

//在ViewModelBase类定义一个私有类,继承ICommand 
 public class RelayCommand : ICommand
//private class _DelegateCommand : ICommand
{
}

再实现一个真正的ViewModle类 class ViewModel : ViewModelBase

当然也可以不实现ViewModelBase,直接在ModelView类里继承INotifyPropertyChanged。

参考微软官方Sample: https://code.msdn.microsoft.com/windowsdesktop/Easy-MVVM-Examples-fb8c409f#content

rainit2006 commented 7 years ago

default

rainit2006 commented 7 years ago

INotifyPropertyChanged The INotifyPropertyChanged (INPC) interface is used for Binding. So, in the average case, you want to implement it in your ViewModel. The ViewModel is used to decouple the Model from your View, so there is no need to have INPC in your Model, as you do not want Bindings to your Model. In most cases, even for smaller properties, you still have a very small ViewModel.

public sealed class Sample : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
}

To implement INotifyPropertyChanged you need to declare the PropertyChanged event and create the OnPropertyChanged method. Then for each property you want change notifications for, you call OnPropertyChanged whenever the property is updated. https://msdn.microsoft.com/ja-jp/library/ms743695(v=vs.110).aspx

PropertyChangedEventHandler Represents the method that will handle the PropertyChanged event raised when a property is changed on a component.

public delegate void PropertyChangedEventHandler(
    object sender,
    PropertyChangedEventArgs e
)
rainit2006 commented 7 years ago

TemplateBinding DataTrigger

rainit2006 commented 7 years ago

UpdateSourceTrigger: Describes the timing of binding source updates. 説明:

Default 
The default UpdateSourceTrigger value of the binding target property. The default value for most dependency properties is PropertyChanged, while the Text property has a default value of LostFocus.
Explicit 
Updates the binding source only when you call the UpdateSource method.
LostFocus 
Updates the binding source whenever the binding target element loses focus. 
PropertyChanged 
Updates the binding source immediately whenever the binding target property changes.
rainit2006 commented 7 years ago

https://code.msdn.microsoft.com/windowsdesktop/Easy-MVVM-Examples-fb8c409f Easy MVVM Demo的研究


#MainWindow.xaml
<windiwos>里指定了“DataContext = {DynamicResource ViewModelMain}”
<listBox ItemsSource = {Binding People} SelectedItem = {Binding SelectedPerson} ...> 

#ViewModelMain.cs
继承INotifyPropertyChanged接口, 实现了一个RaisePropertyChanged函数
包含两个成员变量,对应xaml里bingding的对象
public Observable<Person> People {get; set;}
public object SelectedPerson
{  
  get   _SelectedPerson;  
  set   {if(SelectedPerson != value)  RaisePropertyChanged("SelectedPerson");} 
}

#Model里的Person.cs
class Person : INotifyPropertyChanged
rainit2006 commented 7 years ago

Xaml binding的基础知识

一定要注意: 用的属性,而不是类。特别是ListBox的ListItem绑定ObeservableCollection数据的时候

rainit2006 commented 7 years ago

自定义ResourceDictionary

  1. 工程里创建“Resources”目录,并创建一个xaml文件(TextDictionary.xaml),记入ResourceDictionary及具体要素。
  2. 在App.xaml里写明:
    <Application.Resources>
    <ResourceDictionary>
              <ResourceDictionary.MergedDitionaries>
                      <ResourceDictionary Source="./Resources/TextDictionary.xaml"/>
    ....

3, 在MainWindows.xaml里引用需要的resource <RadioButton Content="{SaticResouce MAIN_WIN_RADIO_01}">

rainit2006 commented 7 years ago

TabView http://blog.okazuki.jp/entry/2014/08/15/232445

<TabControl>
    <TabItem Header="TabItem1">
        <TextBlock Text="TabItem1 Content" />
    </TabItem>
    <TabItem Header="TabItem2">
        <TextBlock Text="TabItem2 Content" />
    </TabItem>
    <TabItem Header="TabItem3">
        <TextBlock Text="TabItem3 Content" />
    </TabItem>
</TabControl>

ListBox ItemsSource プロパティを設定するときに、ItemTemplate を設定して、それぞれの ListBoxItem の表示方法をカスタマイズできます。 https://msdn.microsoft.com/ja-jp/library/cc265158(v=vs.95).aspx 要点: 1,xaml里定义好template,banding数据源(比如customers以及具体的数据项目:name,last name, age) cs(数据源model)里如下。关键词是ObservableCollection。

public class Customers : ObservableCollection<Customer>
{
    public Customers()
    {
        Add(new Customer("Michael", "Anderberg",
                "12 North Third Street, Apartment 45"));
        。。。。
}

ObservableCollection WPFでコレクションのデータバインディングを行う場合はObservableCollectionを使います。

ObservableCollectionを使用しているので、コレクションに追加や削除などを行うと、画面の表示も更新されます。

rainit2006 commented 7 years ago

UserControl UserControlは、複数のコントロールを組み合わせたコントロールを作成するのに向いています。

UserControl1.xaml

<UserControl x:Class="WpfApplication1.UserControl1"
             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="300" d:DesignWidth="300">
    <Grid>
        <StackPanel>
            <Button Content="ボタン" Click="Button_Click" />
            <TextBlock Name="textBlock" />
        </StackPanel>
    </Grid>
    <Style x:Key="...>
           <Setter Property="Margin" Value="2,0,0,0"/>
            .....
    </Style>
</UserControl>

UserControl1.cs

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

namespace WpfApplication1 {
    public partial class UserControl1 : UserControl {
        public UserControl1() {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e) {
            this.textBlock.Text = "こんにちは";
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow">
    <StackPanel>
        <local:UserControl1 />
        <local:UserControl1 />
    </StackPanel>
</Window>

注意: UserControl有一个loaded事件。

自定义控件的样式 参考: http://www.cnblogs.com/anding/p/4968050.html

ItemsControl クラスは、用途に応じて 以下4 つのプロパティを設定し、外観をカスタマイズすることができます。 Template property ItemsPanel property ItemContainerStyle property ItemTemplate property image

rainit2006 commented 7 years ago

Style http://tempprog.hatenablog.com/entry/2015/07/04/204311 Styleの方でx:Keyを指定してあげれば、それを指定された要素にのみそのスタイルが適応されるようになります。Buttonの方でStyleプロパティにStaticResourceで指定してあげます。

<Window x:Class="Style.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">
    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Background" Value="Aqua"/>
                <Setter Property="FontFamily" Value="Comic Sans MS"/>
            </Style>

            <!--x:Key指定-->
            <Style x:Key="special" TargetType="Button">
                <Setter Property="Background" Value="Red"/>
                <Setter Property="FontFamily" Value="Comic Sans MS"/>
                <Setter Property="FontSize" Value="25"/>
            </Style>
        </StackPanel.Resources>

        <Button Content="Button"/>
        <Button Content="YEAH"/>
        <Button Content="Hello" Style="{StaticResource special}" />
    </StackPanel>
</Window>
rainit2006 commented 7 years ago

リソース

-リソースの参照方法

  1. StaticResourceマークアップ拡張を使う方法、

  2. DynamicResourceマークアップ拡張を使う方法、

    <Border x:Name="border" Width="100" Height="100" Background="{StaticResource AnthemBrush}" />
    <Border Width="100" Height="100" Background="{DynamicResource AnthemBrush}" />
  3. プログラムからアクセスする方法。 var brush = (SolidColorBrush)this.FindResource("RedBrush"); this.border.Background = brush;

※DyanmicResourceマークアップ拡張を使うと、例えばアプリケーションのテーマの切り替えといったことが可能になります。 しかし、必要でない限りStaticResourceマークアップ拡張を使うべきです。その理由は、単純にStaticResourceマークアップ拡張のほうがDynamicResourceマークアップ拡張よりもパフォーマンスが良いからです。

rainit2006 commented 7 years ago

ICommand ViewからViewModelへの通知を受け取る場合、ICommandインターフェイスを介して行われるため、この実装も必要となります。

WPFが提供するコマンドには三つのTypeが存在します。

例:定义一个MyCommand类(继承ICommand),在xaml被绑定。

<Button Content="Execute" Height="23" Name="button1" Width="75"
                Command="{Binding Mode=OneWay, Source={StaticResource myCommand}}"               
                CommandParameter="{Binding ElementName=textBox1, Path=Text}" />
rainit2006 commented 7 years ago

DataTemplate

rainit2006 commented 7 years ago

binding converter ConverterはBindingソースと表示形式の間で変換が必要な時に使用します。 image

rainit2006 commented 7 years ago

MultiDataTrigger Represents a trigger that applies property values or performs actions when the bound data meet a set of conditions. (binary AND operation).

image

    <Window.Resources>
        <Style x:Key="MultiBindingSampleStyle" TargetType="Label">
            <Setter Property="Background" Value="#00BCD4" />
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding ElementName=HoverCheckbox, Path=IsChecked}" Value="true" />
                        <Condition Binding="{Binding ElementName=HoverLabel, Path=IsMouseOver}" Value="true" />
                    </MultiDataTrigger.Conditions>
                    <Setter Property="Background" Value="#FF5722" />
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>

    <Grid>
        <CheckBox x:Name="HoverCheckbox" Content="ここがチェックされていている時に" Margin="10,10,0,0" VerticalAlignment="Top" HorizontalAlignment="Left">
            <CheckBox.LayoutTransform>
                <ScaleTransform ScaleX="2" ScaleY="2" />
            </CheckBox.LayoutTransform>
        </CheckBox>
        <Label x:Name="HoverLabel" Content="ここのマウスオーバーで" HorizontalAlignment="Left" Margin="10,49,10,0" VerticalAlignment="Top" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Height="60" Width="452" FontSize="28" Background="#FF00BCD4" Foreground="White"/>
        <Label x:Name="TargetLabel" Content="ここの背景色が変わります" Style="{StaticResource MultiBindingSampleStyle}" HorizontalAlignment="Left" Margin="10,151,10,10" VerticalAlignment="Top" Height="128" Width="452" Foreground="White" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontSize="28" />

    </Grid>
</Window>

想实现OR关系怎么办? 方法1:实装IMultiValueConverter,实现一个convert http://gootara.org/library/2017/01/wpfao.html

方法2:tranform Conditions into two independent DataTriggers

<Style.Triggers>
    <DataTrigger Binding="{Binding CCTVPath}" Value="">
        <Setter Property="Visibility" Value="Hidden"/>
    </DataTrigger>
    <DataTrigger Binding="{Binding PermissionsFlag}" Value="False">
        <Setter Property="Visibility" Value="Hidden"/>
    </DataTrigger>
</Style.Triggers>
rainit2006 commented 7 years ago

MultiBinding 例:複数項目をまとめて、1つの場所に表示したいときに使える

通常のBindingとの違いは、主に3点。

  1. 複数のBindingSourceを指定可能
  2. 複数Sourceの変更通知をトリガーに動作する
  3. MultiValueConverterの指定が必須 通常のBindingの場合、BindingSourceとTargetが1対1なので単純な型変換と代入で済みますが、 Sourceが複数の場合は"標準の動き"というものを定義できないので、IMultiValueConverterインターフェイスを実装したオブジェクトを指定する必要があります。(※しないと例外が発生します)

MultiBindingだとXAMLで以下のように書きます

<TextBlock>
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource xxxConverter}">
            <Binding Path="SourceText1"/>
            <Binding Path="SourceText2"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

MultiValueConverterの実装例

public class xxxConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
  ///[txt1, txt2] -> [txt1:txt2]
        return values.Aggregate((lhs, rhs) => String.Format("{0}:{1}", lhs, rhs));
    }
 
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
      /// [txt1:txt2] -> [txt1, txt2]
        return value.ToString().Split(':');
    }
}
<Label>Enter a Name:</Label>
<TextBox>
  <TextBox.Text>
    <Binding Source="{StaticResource myDataSource}" Path="Name"
             UpdateSourceTrigger="PropertyChanged"/>
  </TextBox.Text>
</TextBox>

<Label>The name you entered:</Label>
<TextBlock Text="{Binding Source={StaticResource myDataSource}, Path=Name}"/>
rainit2006 commented 6 years ago

Binding

使用例: The following example binds a TextBox to a property of an object. If the property is null, the TextBox displays "please enter a string."

<TextBox Width="150"
         Text="{Binding Source={StaticResource object2}, 
  Path=PropertyB, BindingGroupName=bindingGroup, 
  TargetNullValue=please enter a string}" />

そこで、bool? 型と Visibility 型を変換するための値コンバーターを作ります。値コンバーターは IValueConverter インターフェイスを実装するクラスであり、この場合は次のように記述できます。

[コード]

public class BoolVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((bool?)value == true) ? Visibility.Visible : Visibility.Hidden;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

注:Visibility 的三种状态 Collapsed :Do not display the element, and do not reserve space for it in layout. Hidden :Do not display the element, but reserve space for the element in layout. Visible :Display the element.

rainit2006 commented 6 years ago

INotifyPropertyChanged,プロパティ変更通知イベント

public class PersonProperty : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

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

                OnPropertyChanged("Name");
            }
        }
    }
    private string _name;

    public string Age
    {
        get { return _Age; }
        set
        {
            if (_Age != value)
            {
                _Age = value;

                OnPropertyChanged("Age");
            }
        }
    }
    private string _Age;

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

呼び出し側

private void button1_Click(object sender, EventArgs e)
{
    PersonProperty Person = new PersonProperty();

    Person.PropertyChanged += PersonPropertyChanged;
    Person.Name = "みかん";
}

Private void PersonPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    // 文字列でプロパティ名を判別
    if (e.PropertyName != "Name") return;

    var p = (PersonProperty)sender;
    MessageBox.Show(p.Name + "に変更されました");
}
rainit2006 commented 6 years ago

Trigger image

ControlTemplate.Triggers property变更时触发。

<Window x:Class="TemplateSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>

        <!--ボタンのテンプレート-->
        <ControlTemplate x:Key="buttonTemplate" TargetType="{x:Type Button}">

            <!--テンプレート内でアクセスするので名前を付けておく-->
            <!--角を丸めてボタンっぽく見せる-->
            <Border Name="border" CornerRadius="20,20,20,20" BorderThickness="2">

                <!--ボタンの背景を左から右へグラデーションさせる-->
                <Border.Background>
                    <LinearGradientBrush StartPoint="0,1">
                        <GradientStop Color="#0593E2" Offset="0"/>
                        <GradientStop Color="#303030" Offset="1"/>
                    </LinearGradientBrush>
                </Border.Background>

                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Border>
            <ControlTemplate.Triggers>

                <!--ボタンの上にマウスカーソルがあるとき-->
                <Trigger Property="IsMouseOver" Value="True">
                    <!--ボタンの上にマウスカーソルがある間は背景色を変更-->
                    <Setter TargetName="border" Property="Background">
                        <Setter.Value>
                            <LinearGradientBrush StartPoint="0,1">
                                <GradientStop Color="#0593E2" Offset="0"/>
                                <GradientStop Color="#FFFFFF" Offset="1"/>
                            </LinearGradientBrush>
                        </Setter.Value>
                    </Setter>
                </Trigger>

                <!--ボタンが押されたとき-->
                <Trigger Property="IsPressed" Value="True">
                    <!--押されている間は背景色を変更する-->
                    <Setter TargetName="border" Property="Background">
                        <Setter.Value>
                            <LinearGradientBrush StartPoint="0,1">
                                <GradientStop Color="#FF0000" Offset="0"/>
                                <GradientStop Color="#303030" Offset="1"/>
                            </LinearGradientBrush>
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!--Styleを使ってTemplateをまとめて適用させる-->
        <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
            <Setter Property="Template" Value="{StaticResource buttonTemplate}"/>
            <Setter Property="Height" Value="100"/>
            <Setter Property="Margin" Value="10"/>
            <Setter Property="Foreground" Value="#FFFFFF"/>
        </Style>

    </Window.Resources>
    <StackPanel>
        <Button Content="Sample" />
        <Button Content="Test" />
    </StackPanel>
</Window>

image 1番目のウィンドウの下のボタンが、マウスオーバー時のものです。そして2番目のウィンドウの赤いボタンが、押されているボタンです。マウスカーソルが無いのでちょっと分かりづらいですね。

DataTrigger Data变化时触发。通常是ViewModel里的数据发生变化时,View相应地变化。

<Window x:Class="WpfTutorialSamples.Styles.StyleDataTriggerSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleDataTriggerSample" Height="200" Width="200">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <CheckBox Name="cbSample" Content="Hello, world?" />
        <TextBlock HorizontalAlignment="Center" Margin="0,20,0,0" FontSize="48">
            <TextBlock.Style>
                <Style TargetType="TextBlock">
                    <Setter Property="Text" Value="No" />
                    <Setter Property="Foreground" Value="Red" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding ElementName=cbSample, Path=IsChecked}" Value="True">
                            <Setter Property="Text" Value="Yes!" />
                            <Setter Property="Foreground" Value="Green" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </StackPanel>
</Window>

image

we bind the TextBlock to the IsChecked property of the CheckBox. We then supply a default style, where the text is "No" and the foreground color is red, and then, using a DataTrigger, we supply a style for when the IsChecked property of the CheckBox is changed to True, in which case we make it green with a text saying "Yes!"

Event triggers Event发生时触发。

<Window x:Class="WpfTutorialSamples.Styles.StyleEventTriggerSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="StyleEventTriggerSample" Height="100" Width="300">
    <Grid>
        <TextBlock Name="lblStyled" Text="Hello, styled world!" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock.Style>
                <Style TargetType="TextBlock">
                    <Style.Triggers>
                        <EventTrigger RoutedEvent="MouseEnter">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0.300" Storyboard.TargetProperty="FontSize" To="28" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="MouseLeave">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Duration="0:0:0.800" Storyboard.TargetProperty="FontSize" To="18" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                    </Style.Triggers>
                </Style>
            </TextBlock.Style>
        </TextBlock>
    </Grid>
</Window>

As you can see, I use an EventTrigger to subscribe to two events: MouseEnter and MouseLeave. When the mouse enters, I make a smooth and animated transition to a FontSize of 28 pixels in 300 milliseconds. When the mouse leaves, I change the FontSize back to 18 pixels but I do it a bit slower, just because it looks kind of cool.

rainit2006 commented 6 years ago

MarkupExtension XAMLを書いてて、よく出てくる{StaticResource ...}や{DynamicResource ...}や{x:Type ...}のような{}で囲まれた部分。 これってどうやって作るんだろう?と思って調べてみた。 結論としてはMarkupExtentionクラスを継承して作ればOKらしい。

HelloWorldExtensionという名前のクラスをさくっとつくる。 MarkupExtensionを継承すると、ProvideValueメソッドをオーバーライドしないといけない。 このメソッドで返した値が、実際にXAMLに書いたときに使われるみたいだ。ということでHello worldを返すように実装してみた。

using System;  
using System.Windows.Markup;  

namespace WpfMarkupExtension  
{  
    public class HelloWorldExtension : MarkupExtension  
    {  
        public string Prefix { get; set; }  
        public override object ProvideValue(IServiceProvider serviceProvider)  
        {  
            return Prefix + "Hello world";  
        }  
    }  
}  

<Window x:Class="WpfMarkupExtension.Window1"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
    xmlns:WpfMarkupExtension="clr-namespace:WpfMarkupExtension"  
    Title="Window1" Height="300" Width="300">  
    <Grid>  
        <!-- Prefixプロパティ!! -->  
        <TextBlock Text="{WpfMarkupExtension:HelloWorld Prefix=へろー}" />  
            <!-- namespace name : class name ("Extension"可省略) 属性名--> 
    </Grid>  
</Window>  

引数のあるコンストラクタを定義すれば,BindingのPathプロパティみたいに{Binding Text}って書くと{Binding Path=Text}と同じように扱われることができる。 そして、コンストラクタの引数で指定されるプロパティに対してConstructorArgumentもつけてあげるのがいいっぽい。(理由は不明だけど!)

using System;  
using System.Windows.Markup;  

namespace WpfMarkupExtension  
{  
    public class HelloWorldExtension : MarkupExtension  
    {  
        [ConstructorArgument("prefix")]  
        public string Prefix { get; set; }  

        public HelloWorldExtension() { }  

        public HelloWorldExtension(string prefix)  
        {  
            this.Prefix = prefix;  
        }  

        public override object ProvideValue(IServiceProvider serviceProvider)  
        {  
            return Prefix + "Hello world";  
        }  
    }  
}  

<Window x:Class="WpfMarkupExtension.Window1"  
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
    xmlns:WpfMarkupExtension="clr-namespace:WpfMarkupExtension"  
    Title="Window1" Height="300" Width="300">  
    <Grid>  
        <!-- Prefixと明示的に指定しなくてもOK -->  
        <TextBlock Text="{WpfMarkupExtension:HelloWorld へろー}" />  
    </Grid>  
</Window>  
rainit2006 commented 6 years ago

XAMLでの文字列のフォーマット指定とバインディング

<TextBlock Text="{Binding Value, StringFormat={}{0} 円}"/>
<TextBlock Text="{Binding Value, StringFormat=商品A {0} 円}"/>
<TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="First {0} : Second {1}">
            <Binding Path="FirstValue"/>
            <Binding Path="SecondValue"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>
rainit2006 commented 6 years ago

MVVM实例

rainit2006 commented 6 years ago

MVVM实现的例子1

@RelayCommand类的实现:用于实现命令绑定。

  public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion // Fields

        #region Constructors

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }
        #endregion // Constructors

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        #endregion // ICommand Members
    }

ViewModelBase类的实现

 class ViewModelBase : INotifyPropertyChanged
    {
        //basic ViewModelBase
        internal void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        //Extra Stuff, shows why a base ViewModel is useful
        bool? _CloseWindowFlag;
        public bool? CloseWindowFlag
        {
            get { return _CloseWindowFlag; }
            set
            {
                _CloseWindowFlag = value;
                RaisePropertyChanged("CloseWindowFlag");
            }
        }

        public virtual void CloseWindow(bool? result = true)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
            {
                CloseWindowFlag = CloseWindowFlag == null 
                    ? true 
                    : !CloseWindowFlag;
            }));
        }
    }

ViewModelMain类的实现

 class ViewModelMain : ViewModelBase
    {
        public ObservableCollection<Person> People { get; set; }  //People是一个Model类。

        object _SelectedPerson;
        public object SelectedPerson
        {
            get
            {
                return _SelectedPerson;
            }
            set
            {
                if (_SelectedPerson != value)
                {
                    _SelectedPerson = value;
                    RaisePropertyChanged("SelectedPerson");
                }
            }
        }

       string _TextProperty1;
        public string TextProperty1
        {
            get
            {
                return _TextProperty1;
            }
            set
            {
                if (_TextProperty1 != value)
                {
                    _TextProperty1 = value;
                    RaisePropertyChanged("TextProperty1");
                }
            }
        }

        public RelayCommand AddUserCommand { get; set; }
        public ViewModelMain()
        {
            People = new ObservableCollection<Person>
            {
                new Person { FirstName="Tom", LastName="Jones", Age=80 },
                new Person { FirstName="Dick", LastName="Tracey", Age=40 },
                new Person { FirstName="Harry", LastName="Hill", Age=60 },
            };
            TextProperty1 = "Type here";

            AddUserCommand = new RelayCommand(AddUser);
        }

        void AddUser(object parameter)
        {
            if (parameter == null) return;
            People.Add(new Person { FirstName = parameter.ToString(), LastName = parameter.ToString(), Age = DateTime.Now.Second });
        }

}

Model类实现(比如Person类)

    public class Person : INotifyPropertyChanged
    {
        string _FirstName;
        public string FirstName
        {
            get
            {
                return _FirstName;
            }
            set
            {
                if (_FirstName != value)
                {
                    _FirstName = value;
                    RaisePropertyChanged("FirstName");
                }
            }
        }

        string _LastName;
        public string LastName
        {
            get
            {
                return _LastName;
            }
            set
            {
                if (_LastName != value)
                {
                    _LastName = value;
                    RaisePropertyChanged("LastName");
                }
            }
        }

        int _Age;
        public int Age
        {
            get
            {
                return _Age;
            }
            set
            {
                if (_Age != value)
                {
                    _Age = value;
                    RaisePropertyChanged("Age");
                }
            }
        }

        void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}

ViewModelWindow1 类是继承ViewModelMain类的。

   class ViewModelWindow1 : ViewModelMain
    {
        public RelayCommand ChangeTextCommand { get; set; }
        public RelayCommand NextExampleCommand { get; set; }

        string _TestText;
        public string TestText
        {
            get
            {
                return _TestText;
            }
            set
            {
                if (_TestText != value)
                {
                    _TestText = value;
                    RaisePropertyChanged("TestText");
                }
            }
        }

        //This ViewModel is just to duplicate the last, but showing binding in code behind
        public ViewModelWindow1(string lastText)
        {
            _TestText = lastText; //Using internal variable is ok here because binding hasn't happened yet
            ChangeTextCommand = new RelayCommand(ChangeText);
            NextExampleCommand = new RelayCommand(NextExample);
        }

        void ChangeText(object selectedItem)
        {
            //Setting the PUBLIC property 'TestText', so PropertyChanged event is fired
            if (selectedItem == null)
                TestText = "Please select a person"; 
            else
            {
                var person = selectedItem as Person;
                TestText = person.FirstName + " " + person.LastName;
            }
        }

        void NextExample(object parameter)
        {
            var win = new Window2();
            win.Show();
            CloseWindow();
        }
    }
}

在View层里

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

        private void Button_Click(object sender, RoutedEventArgs e)
        {
          。。。。
        }
    }

<Window
          。。。。。。。。
        DataContext="{DynamicResource ViewModelMain}">

<Window.Resources>
        <vm:ViewModelMain x:Key="ViewModelMain"/>
</Window.Resources>

 <GroupBox Header="Classic INotifyPropertyChanged Example" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <ScrollViewer VerticalScrollBarVisibility="Auto">
                    <StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <ListBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}" DisplayMemberPath="FirstName" HorizontalAlignment="Left"/>
                            <DataGrid ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}" HorizontalAlignment="Left" Margin="5,0,0,0"/>
                            <ComboBox ItemsSource="{Binding People}" SelectedItem="{Binding SelectedPerson}" DisplayMemberPath="FirstName" Margin="5,0,0,5" VerticalAlignment="Top"/>
                        </StackPanel>
                        <TextBox x:Name="tb1" Text="{Binding TextProperty1, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
                        <TextBlock FontWeight="Bold" Margin="5" Text="The TextBox says '"><Run Text="{Binding TextProperty1}"/><Run Text="'"/></TextBlock>
                    </StackPanel>
                </ScrollViewer>
                <Button Grid.Row="1" Content="Add person" Command="{Binding AddUserCommand}" CommandParameter="{Binding Text, ElementName=tb1}" Margin="5" Focusable="False"/>
            </Grid>
        </GroupBox>
        <Button Content="Next example" VerticalAlignment="Top" HorizontalAlignment="Right" FontWeight="Bold" Foreground="Red" Click="Button_Click" Grid.Row="1" Margin="5"/>
rainit2006 commented 6 years ago

实例2 ModelViewBase类的实现: 可以参考:http://sourcechord.hatenablog.com/entry/20130303/1362315081

public abstract class BindableBase : INotifyPropertyChanged
    {
        /// <summary>
        /// プロパティの変更を通知するためのマルチキャスト イベント。
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// プロパティが既に目的の値と一致しているかどうかを確認します。必要な場合のみ、
        /// プロパティを設定し、リスナーに通知します。
        /// </summary>
        /// <typeparam name="T">プロパティの型。</typeparam>
        /// <param name="storage">get アクセス操作子と set アクセス操作子両方を使用したプロパティへの参照。</param>
        /// <param name="value">プロパティに必要な値。</param>
        /// <param name="propertyName">リスナーに通知するために使用するプロパティの名前。
        /// この値は省略可能で、
        /// CallerMemberName をサポートするコンパイラから呼び出す場合に自動的に指定できます。</param>
        /// <returns>値が変更された場合は true、既存の値が目的の値に一致した場合は
        /// false です。</returns>
        protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] String propertyName = null, bool needFire = true)
        {
            //if (object.Equals(field, value)) return false;
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;

            field = value;
            if (needFire)
                this.OnPropertyChanged(propertyName);
            return true;
        }
        protected bool SetProperty<T>(ref T field, T value, Expression<Func<T>> selectorExpression, bool needFire = true)
        {
            //if (object.Equals(field, value)) return false;
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;

            field = value;
            if (needFire)
                this.OnPropertyChanged(selectorExpression);
            return true;
        }

        /// <summary>
        /// プロパティ値が変更されたことをリスナーに通知します。
        /// </summary>
        /// <param name="propertyName">リスナーに通知するために使用するプロパティの名前。
        /// この値は省略可能で、
        /// <see cref="CallerMemberNameAttribute"/> をサポートするコンパイラから呼び出す場合に自動的に指定できます。</param>
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.PropertyChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
        {
            if (selectorExpression == null)
                throw new ArgumentNullException("selectorExpression");
            MemberExpression body = selectorExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("The body must be a member expression");
            OnPropertyChanged(body.Member.Name);
        }

        public static string GetPropertyName<T>(Expression<Func<T>> selectorExpression)
        {
            if (selectorExpression == null)
                throw new ArgumentNullException("selectorExpression");
            MemberExpression body = selectorExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("The body must be a member expression");
            return body.Member.Name;
        }
    }

ModelView类的实现

   public class PackageViewModel : BindableBase
    {
        。。。。。。。。。
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        PackageModel _packageModel;
        public PackageModel PackageModel
        {
            get { return _packageModel; }
            private set
            {
                if (this.SetProperty(ref _packageModel, value))
                {
                    SignatureInfoSelectedIndex = 0;
                }
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly PackageSettingsModel _settingsModel;
        public PackageSettingsModel Settings
        {
            get { return this._settingsModel; }
        }
。。。。

里面包含PackageModel 类和 PackageSettingsModel 类。其中 PackageSettingsModel 类如下:

    public class PackageSettingsModel : BindableBase
    {
        #region Constructors & Destructor

        public PackageSettingsModel()
        {
            this._isModified = false;

            this._DateTime = null;
            this._originalDateTime = null;
            this._originalVersion = null;
           ........
        }

      #region IsModified
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        bool _isModified;
        public bool IsModified
        {
            get { return this._isModified; }
            private set { this.SetProperty(ref this._isModified, value); }
        }
        #endregion

        #region DateTime
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        string _DateTime;
        public string DateTime
        {
            get { return this._DateTime; }
            set
            {
                if (this.SetProperty(ref this._DateTime, value))
                {
                    this.IsModified = true;
                }
            }
        }
        #endregion
       ..........

        public bool SaveSetting(string filePath)
        {  保存情报 }
}

View层

   public partial class MainWindow : Window
    {
        #region Constructors & Destructor

        public MainWindow()
        {
            _viewModel = new PackageViewModel();
            this.DataContext = _viewModel;

            InitializeComponent();
        }

         readonly PackageViewModel _viewModel;
 <TextBox Grid.Row="0" Grid.Column="1"
                                 Text="{Binding PackageModel.FileSize,Mode=OneWay,StringFormat={}{0:N0}}"/>

 <Grid Grid.Column="0" DataContext="{Binding PackageModel.Settings}">
          <TextBox Grid.Row="0" Grid.Column="1"
                             Text="{Binding DateTime,Mode=OneWay}"/>