OxyPlot : First Meet

OxyPlot : First Meet

OxyPlot.Wpf

OxyPlot.Wpf 套件可以用來在WPF程式中表現圖形,圖形的種類有AreaSeries、BarSeries、BoxPlotSeries、 HeatMapSeries、LineSeries、PieSeries、ScatterSeries、ThreeColorLineSeries、 TwoColorLineSeries、TwoColorAreaSeries等。

介紹

OxyPlot首頁裡面的說明文件剛開始構建,內容不多。 OxyPlot的最上層為PlotModel,它的屬性有

  • Axes collection
  • Series collection
  • Annotations collection
  • fonts
  • Default Colors
  • Margins
  • Legends

實作說明

視模端

視模端我們準備下列公開的屬性:

  • 由 ColumnItem 組成的躉集 PercentagesH
  • 由 ColumnItem 組成的躉集 PercentagesL
  • 由 string 組成的躉集 ChoiceSymbols
  • 由 DataPoint 組成的躉集 Points
  • 由 DataPoint 組成的躉集 Ps,DataPoint的橫坐標依序為0,1,2,...。

視圖端

LineSeries 1

下面的例子,畫的是折線圖,資料點的位置用小圓點表示。

<oxy:Plot Title="LineSeries" >
    <oxy:Plot.Series>
        <oxy:LineSeries ItemsSource="{Binding Points}" MarkerType="Circle" MarkerSize="3"/>
    </oxy:Plot.Series>
</oxy:Plot>

ColumnSeries

下面的例子,畫的是柱狀圖。柱狀圖由兩組資料組成,兩組資料並排呈現,用顏色區分組別。 橫軸用CategoryAxis,縱軸用LinearAxis。

<oxy:Plot Title="ColumnSeries" Grid.Column="1" LegendPosition="RightTop" >
    <oxy:Plot.Series>
        <oxy:ColumnSeries ItemsSource="{Binding PercentagesL}" LabelFormatString="{}{0}" Title="L"/>
        <oxy:ColumnSeries ItemsSource="{Binding PercentagesH}" LabelFormatString="{}{0}" Title="H"/>
    </oxy:Plot.Series>
    <oxy:Plot.Axes>
        <oxy:CategoryAxis Position="Bottom" Title="選項" Labels="{Binding ChoiceSymbols}" />
        <oxy:LinearAxis Position="Left" Minimum="0" Maximum="100" Title="選取率%" />
    </oxy:Plot.Axes>
</oxy:Plot>

註解:在xaml裡面,{是保留符號,但是LabelFormatString要用string.Format裡的 { 來格式化輸出,因此在格式字串的開頭要加上額外的'{}'來進行脫逃(escape)避免衝突。

LineSeries 2

下面的例子,畫的是折線圖,資料點的位置用小方塊表示。 橫軸用CategoryAxis,縱軸用LinearAxis。

<oxy:Plot Title="LineSeries" >
    <oxy:Plot.Series>
        <oxy:LineSeries ItemsSource="{Binding Ps}" MarkerType="Square"  MarkerSize="3" />
    </oxy:Plot.Series>
    <oxy:Plot.Axes>
        <oxy:CategoryAxis Labels="{Binding ChoiceSymbols}"/>
    </oxy:Plot.Axes>
</oxy:Plot>

實作程式碼

視圖端 MainWindow.xaml

<Window x:Class="testOxyPlot.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:oxy="http://oxyplot.org/wpf"
        xmlns:local="clr-namespace:testOxyPlot"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
            <TextBox Text="{Binding X}" Width="60" />
            <TextBox Text="{Binding Y}" Width="60" />
            <Button Content="Add Point" Command="{Binding AddPointCommand}" />
        </StackPanel>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <oxy:Plot Title="LineSeries"  DockPanel.Dock="Top">
                <oxy:Plot.Series>
                    <oxy:LineSeries ItemsSource="{Binding Points}" MarkerType="Circle" MarkerSize="3"/>
                </oxy:Plot.Series>
            </oxy:Plot>
            <oxy:Plot Title="ColumnSeries" Grid.Column="1" LegendPosition="RightTop" >
                <oxy:Plot.Series>
                    <oxy:ColumnSeries ItemsSource="{Binding PercentagesL}" LabelFormatString="{}{0}" Title="L"/>
                    <oxy:ColumnSeries ItemsSource="{Binding PercentagesH}" LabelFormatString="{}{0}" Title="H"/>
                </oxy:Plot.Series>
                <oxy:Plot.Axes>
                    <oxy:CategoryAxis Position="Bottom" Title="選項" Labels="{Binding ChoiceSymbols}" />
                    <oxy:LinearAxis Position="Left" Minimum="0" Maximum="100" Title="選取率%" />
                </oxy:Plot.Axes>
            </oxy:Plot>
            <oxy:Plot Title="LineSeries" Grid.Row="1"  >
                <oxy:Plot.Series>
                    <oxy:LineSeries ItemsSource="{Binding Ps}" MarkerType="Square"  MarkerSize="3" />
                </oxy:Plot.Series>
                <oxy:Plot.Axes>
                    <oxy:CategoryAxis Labels="{Binding ChoiceSymbols}"/>
                </oxy:Plot.Axes>
            </oxy:Plot>
        </Grid>
    </DockPanel>
</Window>

視模端 MainWindowViewModel.cs

using OxyPlot;
using OxyPlot.Series;
using Prism.Commands;
using Prism.Mvvm;
using System.Collections.ObjectModel;
namespace testOxyPlot
{
    public class MainWindowViewModel : BindableBase
    {
        public MainWindowViewModel()
        {
            Points = new ObservableCollection<DataPoint> {
            new DataPoint(0, 4),
                                  new DataPoint(10, 13),
                                  new DataPoint(20, 15),
                                  new DataPoint(30, 16),
                                  new DataPoint(40, 12),
                                  new DataPoint(50, 12)
            };
            _PercentagesH = new ObservableCollection<ColumnItem>
            {
                new ColumnItem(22),
                new ColumnItem(30),
                new ColumnItem(50),
                new ColumnItem(80),
                new ColumnItem(12)
            };
            _PercentagesL = new ObservableCollection<ColumnItem>
            {
                new ColumnItem(12),
                new ColumnItem(10),
                new ColumnItem(30),
                new ColumnItem(40),
                new ColumnItem(5)
            };
            _ChoiceSymbols = new ObservableCollection<string> { "A", "B", "C", "D", "E" };
            _Ps = new ObservableCollection<DataPoint>
            {
                new DataPoint(0,10),
                new DataPoint(1,15),
                new DataPoint(2,40),
                new DataPoint(3,60),
                new DataPoint(4,90)
            };
            Title = "HJY";
            AddPointCommand = new DelegateCommand(() =>
            {
                Points.Add(new DataPoint(_X, _Y));
                PercentagesL.Add(new ColumnItem(_X));
                PercentagesH.Add(new ColumnItem(_Y));
            });
        }
        private int _X;
        public int X
        {
            get { return _X; }
            set { SetProperty(ref _X, value); }
        }
        private int _Y;
        public int Y
        {
            get { return _Y; }
            set { SetProperty(ref _Y, value); }
        }
        private ObservableCollection<ColumnItem> _PercentagesH;
        public ObservableCollection<ColumnItem> PercentagesH
        {
            get { return _PercentagesH; }
            set { SetProperty(ref _PercentagesH, value); }
        }
        private ObservableCollection<ColumnItem> _PercentagesL;
        public ObservableCollection<ColumnItem> PercentagesL
        {
            get { return _PercentagesL; }
            set { SetProperty(ref _PercentagesL, value); }
        }
        public DelegateCommand AddPointCommand { get; private set; }
        public string Title { get; private set; }
        public ObservableCollection<DataPoint> Points { get; private set; }
        private ObservableCollection<string> _ChoiceSymbols;
        public ObservableCollection<string> ChoiceSymbols
        {
            get { return _ChoiceSymbols; }
            set { SetProperty(ref _ChoiceSymbols, value); }
        }
        private ObservableCollection<DataPoint> _Ps;
        public ObservableCollection<DataPoint> Ps
        {
            get { return _Ps; }
            set { SetProperty(ref _Ps, value); }
        }
    }
}
AutoCompleteTextBox: First Meet

AutoCompleteTextBox: First Meet

AutoCompleteTextBox in DotNetProjects.Wpf.Toolkit

DotNetProjects.Wpf.Toolkit 套件提供了AutoCompleteTextBox,它是一個可以在 WPF 程式裡面使用的控制項。除了像TextBox一樣,提供使用者輸入文字外,在使用者輸入文字的同時,能根據使用者的輸入及時提供符合自訂條件的建議字串清單,加快使用者的輸入速度。

要使用AutoCompleteTextBox,使用者必須先提供候選的清單以及過濾的方法。

實作過程

  • 新開始名為TestAutoCompleteTextBox的WPF Application方案
  • 在TestAutoCompleteTextBox專案中用Nuget Manager 加入DotNetProjects.Wpf.Toolkit
  • 在TestAutoCompleteTextBox專案中加入類別檔案MainWindowViewModel.cs用來當作MainWindow.xaml的視模(ViewModel)
  • 在MainWindowViewModel類別實作
    • INotifyPropertyChanged介面
    • 可當作繫結來源屬性的Name,類型為string
    • 可當作繫結來源屬性的Names屬性,類型為List<string> ,用Names來當作候選清單
    • 可當作繫結來源的Filter屬性,Filter是個過濾器,利用使用者的輸入內容,從候選清單Names中挑出子清單供使用者挑選。
  • 在 MainWindow.xaml 中:
    • Window的屬性增加 xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=DotNetProjects.Input.Toolkit"
    • Window的Resources裡面增加資源<local:MainWindowViewModel x:Key="viewmodel" />
    • 設定Window裡的DataContex為資源裡的viewmodel
    • Window裡增加AutoCompleteTextBox的案例。將其屬性Text以雙向繫結的方式與視模端的來源屬性Name。將其屬性ItemFilter與視模端的來源屬性Filter繫結。將其屬性ItemSource與視模端的來源屬性Names繫結。將MinimumPrefixLength屬性設定為"1",代表使用者只要打1個字元,AutoCompleteTextBox就開始提供合格清單。

程式碼

MainWindowViewModel.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls;
namespace TestAutoCompleteTextBox
{
    public class MainWindowViewModel:INotifyPropertyChanged
    {
        public MainWindowViewModel()
        {
            Names = new List<string>
            {
                "WPF","WCF","DotNet","Data Entity Framework"
            };
        }
        public List<string> Names { get; private set; }
        public event PropertyChangedEventHandler PropertyChanged;
        private string _Name;
        public string Name {
            get
            {
                return _Name;
            }
            set
            {
                if (value != _Name)
                {
                    _Name = value;
                    OnPropertyChanged("Name");
                }
            }
        }
        private void OnPropertyChanged(string PropertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
        }
        public AutoCompleteFilterPredicate<object> Filter
        {
            get
            {
                return (searchText, obj) => filter(searchText, obj);
            }
        }
        public bool filter(string editString, object obj)
        {
            string s = obj as string;
            return s.ToLower().Contains(editString.ToLower());
        }
    }
}

MainWindow.xaml

<Window x:Class="TestAutoCompleteTextBox.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:local="clr-namespace:TestAutoCompleteTextBox"
  xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=DotNetProjects.Input.Toolkit"  
  mc:Ignorable="d"
  Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <local:MainWindowViewModel x:Key="viewmodel" />
  </Window.Resources>
  <StackPanel DataContext="{StaticResource viewmodel}">
    <TextBlock Text="{Binding Name}" />
    <StackPanel Orientation="Horizontal">
      <TextBlock Text="Input:" />
      <Controls:AutoCompleteBox Width="200"
        Text="{Binding Name, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
        ItemFilter="{Binding Filter}"
        ItemsSource="{Binding Names}"
        MinimumPrefixLength="1"   />
    </StackPanel>
  </StackPanel>
</Window>

註:假如細節的清單由物件所構成,則AutoCompleteTextBox可利用ItemTemplate屬性與ValueMemberPath屬性來完成任務。其中,ItemTemplate用來表現清單裡的物件,ValueMemberPath用來將所選物件之ValueMemberPath對應屬性值複製到AutoCompleteTextBox的Text屬性。

WPF Data Validation

WPF Data Validation

WPF Data Validation

確認使用者輸入的資料是否合乎規格是一個重大的課題,WPF 提供資料驗證(Data Validation)的辦法 。

WPF 透過驗證規則(Validation Rule)來進行驗證。內建的驗證規則有 ExceptionValidationRule 與 DataErrorValidationRule,程式師可以製造自己的客製化驗證規則(Custom Validation Rule)。

資料驗證在將資料更新到來源端時發生,因此 BindingMode 為 TwoWay或 OneWayToSource 時會使用到資料驗證。

要了解資料驗證如何運作,必須了解資料驗證的過程。

資料驗證過程(Validation Process)

根據 Data Binding Voweview,資料驗證依據下列步驟進行。

  1. The binding engine checks if there are any custom ValidationRule objects defined whose ValidationStep is set to RawProposedValue for that Binding, in which case it calls the Validate method on each ValidationRule until one of them runs into an error or until all of them pass.
  2. The binding engine then calls the converter, if one exists.
  3. If the converter succeeds, the binding engine checks if there are any custom ValidationRule objects defined whose ValidationStep is set to ConvertedProposedValue for that Binding, in which case it calls the Validate method on each ValidationRule that has ValidationStep set to ConvertedProposedValue until one of them runs into an error or until all of them pass.
  4. The binding engine sets the source property.
  5. The binding engine checks if there are any custom ValidationRule objects defined whose ValidationStep is set to UpdatedValue for that Binding, in which case it calls the Validate method on each ValidationRule that has ValidationStep set to UpdatedValue until one of them runs into an error or until all of them pass. If a DataErrorValidationRule is associated with a binding and its ValidationStep is set to the default, UpdatedValue, the DataErrorValidationRule is checked at this point. This is also the point when bindings that have the ValidatesOnDataErrors set to true are checked.
  6. The binding engine checks if there are any custom ValidationRule objects defined whose ValidationStep is set to CommittedValue for that Binding, in which case it calls the Validate method on each ValidationRule that has ValidationStep set to CommittedValue until one of them runs into an error or until all of them pass.

內建驗證規則中,ExceptionValidationRule 在步驟4發生作用,DataErrorValidationRule 在步驟5發生作用。

  • A ExceptionValidationRule checks for exceptions thrown during the update of the binding source property.
  • A DataErrorValidationRule object checks for errors that are raised by objects that implement the IDataErrorInfo interface.

實作

這裡,利用 Prism 6來進行實作。

ViewA 裡面有兩個需要輸入資料的文字方塊,前者的資料繫結來源需要長度介於1到10的字串,後者的資料繫結來源類別為int?而且若有數值則其值必須介於10與50之間。另外又一個Button,該button在前面兩文字方塊的內容都符合規格時才有能(Enable)。

ViewTop 有一個 Button 及一個ViewA。該Button在ViewA裡的兩個文字方塊的內容都符合規格時才有能(Enable)。

ViewModel端

ViewA 的 ViewModel 是 ViewAViewModel,其程式碼如下:

public class ViewAViewModel : BindableBase, IDataErrorInfo
{
    private readonly IDictionary<string, string> errors = new Dictionary<string, string>();
    public event EventHandler ErrorChanged;
    public ViewAViewModel()
    {
        SaveCommand = new DelegateCommand(Save, CanSave);
        Validate();
        PropertyChanged += OnPropertyChanged;
    }
    private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        this.Validate();
        this.SaveCommand.RaiseCanExecuteChanged();
        OnErrorChanged(null);
    }
    private string _Message;
    public string Message
    {
        get { return _Message; }
        set { SetProperty(ref _Message, value); }
    }
    private int? _Price;
    public int? Price
    {
        get { return _Price; }
        set { SetProperty(ref _Price, value); }
    }
    #region IDataErrorInfo Interface
    public string this[string columnName]
    {
        get
        {
            if (this.errors.ContainsKey(columnName))
            {
                return this.errors[columnName];
            }
            return null;
        }
        set
        {
            this.errors[columnName] = value;
        }
    }
    public string Error
    {
        get
        {
            // Not implemented because we are not consuming it in this quick start.
            // Instead, we are displaying error messages at the item level.
            throw new NotImplementedException();
        }
    }
    #endregion
    public DelegateCommand SaveCommand { get; private set; }
    public int GetErrorCount()
    {
        return this.errors.Count;
    }
    private void Save()
    {
    }
    private bool CanSave()
    {
        return this.errors.Count == 0;
    }
    private void Validate()
    {
        if (this.Price != null && (this.Price < 10 || this.Price>50))
        {
            this["Price"] = "Price: null or integer between 10 and 50";
        }
        else
        {
            this.ClearError("Price");
        }
        if (string.IsNullOrEmpty(this.Message) || this.Message.Length>10)
        {
            this["Message"] = "Message: non null string with length between 1 and 10.";
        }
        else
        {
            this.ClearError("Message");
        }
    }
    private void ClearError(string columnName)
    {
        if (this.errors.ContainsKey(columnName))
        {
            this.errors.Remove(columnName);
        }
    }
    protected virtual void OnErrorChanged(EventArgs e)
    {
        ErrorChanged?.Invoke(this, e);
    }
}

說明如下:

  • public string Message;當作第一個文字方塊繫結的路徑(Path),用 private int? _Price; 當作第二個文字方塊繫結的路徑。
  • 利用private readonly IDictionary<string, string> errors 實做介面 IDataErrorInfo 以便提供 DataErrorValidationRule。
  • 利用 public event EventHandler ErrorChanged; 提供外界註冊資料驗證改變事件的處理函數。
  • 每次資料改變時,在私有函數 OnPropertyChanged 中,利用私有的 Vilidate 函數更新資料的驗證結果,並且執行資料驗證改變事件(ErrorChanged)的處理函數。
  • 提供 public int GetErrorCount() 供外界了解資料錯誤的數目。

另一方面,ViewTop 的 ViewModel 是 ViewTopViewModel,其程式碼如下:

public class ViewTopViewModel : BindableBase
{
    private string _Message;
    public string Message
    {
        get { return _Message; }
        set { SetProperty(ref _Message, value); }
    }
    public ViewTopViewModel()
    {
        _ChildVM = new ViewAViewModel();
        _ChildVM.ErrorChanged += _ChildVM_ErrorChenged;
        SaveAllCommand = new DelegateCommand(() => { Message = "Done"; }, () => { return ChildVM.GetErrorCount() == 0; });
    }
    private void _ChildVM_ErrorChenged(object sender, EventArgs e)
    {
        SaveAllCommand.RaiseCanExecuteChanged();
    }
    public DelegateCommand SaveAllCommand { get; private set; }
    private ViewAViewModel _ChildVM;
    public ViewAViewModel ChildVM
    {
        get { return _ChildVM; }
        set { SetProperty(ref _ChildVM, value); }
    }
}

說明如下:

  • _ChileVM 是 ViewAViewModel的一個案例。
  • SaveAllCommand 是一個ICommand的案例。
  • _ChildVM.ErrorChanged += _ChildVM_ErrorChenged;註冊_ChildVM.ErrorChanged事件的處理函數。
  • 在_ChildVM_ErrorChenged函數裡,呼叫 SaveAllCommand.RaiseCanExecuteChanged 讓 SaveAllCommand有能(Enable)或失能(Disable)。

View端

ViewA 的程式碼如下:

<UserControl x:Class="ModuleA.Views.ViewA"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:validate="clr-namespace:ModuleA.Validates"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <UserControl.Resources>
        <!-- Reference: http://www.codeproject.com/Tips/858492/WPF-Validation-Using-IDataErrorInfo -->
        <Style x:Key="TextErrorStyle" TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <!--<Setter Property="Background" Value="Red"/>-->
                    <Setter Property="ToolTip"
        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
        Path=(Validation.Errors)[0].ErrorContent}"></Setter>
                </Trigger>
            </Style.Triggers>
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate >
                        <DockPanel>
                            <Border BorderBrush="Red" BorderThickness="1" Padding="2" CornerRadius="2">
                                <AdornedElementPlaceholder/>
                            </Border>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <validate:StringToNullableNumberConverter x:Key="NullableNumberConverter" />
    </UserControl.Resources>
    <Grid>
        <StackPanel>
            <StackPanel Orientation="Horizontal" Margin="3">
                <TextBlock Text="Message" Width="60"  />
                <TextBox  Width="200" Text="{Binding Message, UpdateSourceTrigger=PropertyChanged,
                    ValidatesOnDataErrors=True,ValidatesOnExceptions=True}"
                    Style="{StaticResource TextErrorStyle}"
                    />
            </StackPanel>
            <StackPanel Orientation="Horizontal"  Margin="3">
                <TextBlock Text="Price"   Width="60" />
                <TextBox Width="200" Text="{Binding Price,  UpdateSourceTrigger=PropertyChanged,
                    ValidatesOnDataErrors=True, ValidatesOnExceptions=True,Converter={StaticResource NullableNumberConverter }}"
                    Style="{StaticResource TextErrorStyle}"
                    />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button Content="Save" Command="{Binding SaveCommand}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>

說明如下:

  • <Style x:Key="TextErrorStyle" ...</style> 定義了資料驗證失敗時的樣式,取名TextErrorStyle。這個樣式在資料驗證失敗時,將控制項用紅框框起來,並且用Tooltip提供資料驗證失敗的原因。
  • <validate:StringToNullableNumberConverter x:Key="NullableNumberConverter" />提供資料轉換(Converter)StringToNullableNumberConverter的案例,取名NullableNumberConverter供繫結時的資料轉換使用(資料驗證過程的第 2 步)
  • <TextBox Width="200" Text="{Binding Message, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True,ValidatesOnExceptions=True}" Style="{StaticResource TextErrorStyle}" /> 將第一個文字方塊的內容繫結到 ViewAViewModel 的 Message 屬性,樣式採用 TextErrorStyle 。
  • <TextBox Width="200" Text="{Binding Price, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True,Converter={StaticResource NullableNumberConverter }}" Style="{StaticResource TextErrorStyle}" /> 將第二個文字方塊的內容繫結到 ViewAViewModel 的 Price 屬性,樣式採用TextErrorStyle。另外還使用NullableNumberConverter進行資料轉換。

ViewTop 的程式碼如下:

<UserControl x:Class="ModuleA.Views.ViewTop"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:views="clr-namespace:ModuleA.Views"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
            <Button Command="{Binding SaveAllCommand}" Content="SaveAll" />
            <TextBox Text="{Binding Message}" Width="100"/>
        </StackPanel>
        <views:ViewA DataContext="{Binding ChildVM}" />
    </DockPanel>
</UserControl>

說明如下:

  • <Button Command="{Binding SaveAllCommand}" Content="SaveAll" /> 將Buttom的Command繫結到ViewTopViewModel的SaveCommand。
技術提供:Blogger.