Open rainit2006 opened 7 years ago
要了解MVVM的实现思路 WPF性能差的原因,目前改善了吗
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
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
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
)
TemplateBinding DataTrigger
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.
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
Xaml binding的基础知识
WPF binds only to properties
如果想实现: <TextBox Text="{Binding Path=Name2}"/>
不能够:public String Name2;
而要:
partial class Window1 : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// usual OnPropertyChanged implementation
private string _name2;
public string Name2
{
get { return _name2; }
set
{
if (value != _name2)
{
_name2 = value;
OnPropertyChanged("Name2");
}
}
}
}
或者另外定一个类来实现绑定目标的类和属性: 用到 public string Name2 { get; set; } 在ViewModel对应的cs文件里,追加“this.DataContext = 包含上述property的数据类的对象;” 注意:xaml里务必要指定:xmlns:src="clr-namespace: 命名空间" 这里的xmlns:src 也可以自己命名其他名字,比如xmlns:my。 https://www.ipentec.com/document/document.aspx?page=csharp-wpf-textblock-data-bind-using-xaml
一定要注意: 用的属性,而不是类。特别是ListBox的ListItem绑定ObeservableCollection
自定义ResourceDictionary
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDitionaries>
<ResourceDictionary Source="./Resources/TextDictionary.xaml"/>
....
3, 在MainWindows.xaml里引用需要的resource
<RadioButton Content="{SaticResouce MAIN_WIN_RADIO_01}">
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を使用しているので、コレクションに追加や削除などを行うと、画面の表示も更新されます。
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
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>
リソース
<Application x:Class="ResourceSample01.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ImageBrush x:Key="AnthemBrush" ImageSource="anthem.jpg" />
<SolidColorBrush x:Key="RedBrush" Color="Red" />
</Application.Resources>
</Application>
リソースは、x:Key属性で名前をつけて定義します. App.xamlにリソースを定義すると、全てのWindowから共通で使用できるというメリットがあります。共通で使用するリソースはApp.xamlで定義をして、 Window固有のリソースはWindowで定義して利用するのが一般的です。 例外的な使い方として、各コントロールのResourcesプロパティにリソースを定義する方法もありますが、この場合、そのコントロール内でしか利用できないリソースとなります。
-リソースの参照方法
StaticResourceマークアップ拡張を使う方法、
DynamicResourceマークアップ拡張を使う方法、
<Border x:Name="border" Width="100" Height="100" Background="{StaticResource AnthemBrush}" />
<Border Width="100" Height="100" Background="{DynamicResource AnthemBrush}" />
プログラムからアクセスする方法。
var brush = (SolidColorBrush)this.FindResource("RedBrush"); this.border.Background = brush;
※DyanmicResourceマークアップ拡張を使うと、例えばアプリケーションのテーマの切り替えといったことが可能になります。 しかし、必要でない限りStaticResourceマークアップ拡張を使うべきです。その理由は、単純にStaticResourceマークアップ拡張のほうがDynamicResourceマークアップ拡張よりもパフォーマンスが良いからです。
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}" />
DataTemplate
binding converter ConverterはBindingソースと表示形式の間で変換が必要な時に使用します。
MultiDataTrigger Represents a trigger that applies property values or performs actions when the bound data meet a set of conditions. (binary AND operation).
<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>
MultiBinding 例:複数項目をまとめて、1つの場所に表示したいときに使える
通常のBindingとの違いは、主に3点。
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}"/>
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}" />
fallback value Binding できないときのデフォルト値. たとえば、ListBox に IsSynchronizedWithCurrentItem="True" をつけて、画面のどこかに選択している Item の詳細を表示することにしたけど、ListBoxで何も選択されていないとき.. のデフォルト値は、Binding.FallbackValue に書く。 Listで選択した Item の IsA がTrueのときは表示、Falseのときは非表示、 Listで何も選ばれていないときも非表示にする場合のサンプル
<Canvas Visibility="{Binding Path=IsA,
Converter={StaticResource BooleanToVisibleConverter},
FallbackValue=Collapsed}"
以下略
Converter たとえば、CheckBox コントロールの IsChecked プロパティと、TextBlock コントロールの Visibility プロパティを連動させることを考えてみます。 ここで、次のように記述しても動作しません。IsChecked プロパティは bool? 型、Visibility プロパティは Visibility型であり、型がまったく異なるためです。
そこで、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.
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 + "に変更されました");
}
Trigger
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>
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>
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.
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>
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>
MVVM实例
Reading a .csv file into WPF application (C#) https://stackoverflow.com/questions/21813461/reading-a-csv-file-into-wpf-application-c
展示一个图片浏览器,打开图片,放大/缩小图片大小 http://www.jb51.net/article/70597.htm
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"/>
实例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}"/>
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プロパティが自動的に使われるため以下のように書くことも出来ます。
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