像其他人所建議的那樣,您可以在此方案中使用IMultiValueConverter
。但我認爲,儘管在其他情況下是一個有用的工具,但這是這裏工作的錯誤工具。原因在於,在這種情況下,它將被用來長期不恰當地使用UI元素作爲存儲非UI數據的地方。
在編寫WPF程序時,如果您承諾遵循MVVM風格的編程原則,WPF將用於更好的服務。術語「MVVM」意思是字面上的「模型,視圖,視圖模型」。從這個角度來看,模型和視圖之間總是會有特殊用途的「適配器」類型。但是我的經驗是,MVVM範例的重要部分是嚴格保持視圖邏輯與模型邏輯分離,而且這通常可以在沒有額外的「視圖模型」類型層的情況下完成。這使MVVM與MVC(「模型,視圖,控制器」)和MVP(「模型,視圖,演示者」)在同一套工具中。
所有這些的關鍵在於你有一些業務邏輯,這些業務邏輯在模型數據結構中表示,由提供某種形式的值更改通知的類型實現(在WPF中,此處的主要機制是INotifyPropertyChanged
),以及那麼也可以查看完全獨立表示的邏輯(在WPF中,視圖主要是,而且在很多情況下完全是在XAML中聲明的)。
在您的示例中,這意味着我們需要一個表示您感興趣的數據的模型數據結構:樣本,校準器,控件和總準備計數。最後一個只是前三個的總和。只要我們有一個可以跟蹤這些事件的類,並且在其他三個事件中的任何一個發生變化時正確地更新總和值,我們就可以直接將它與XAML中聲明的視圖綁定,而無需使用任何C#代碼所有。
例如:
class ViewModel : INotifyPropertyChanged
{
private int _sampleCount;
public int SampleCount
{
get { return _sampleCount; }
set { _UpdateField(ref _sampleCount, value, OnCountChanged); }
}
private int _calibratorCount;
public int CalibratorCount
{
get { return _calibratorCount; }
set { _UpdateField(ref _calibratorCount, value, OnCountChanged); }
}
private int _controlCount;
public int ControlCount
{
get { return _controlCount; }
set { _UpdateField(ref _controlCount, value, OnCountChanged); }
}
private int _totalPrepCount;
public int TotalPrepCount
{
get { return _totalPrepCount; }
set { _UpdateField(ref _totalPrepCount, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnCountChanged(int previousValue)
{
TotalPrepCount = SampleCount + CalibratorCount + ControlCount;
}
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
注:
- 上面這個類有四個屬性,一個屬性每次我們想要跟蹤的值。
- 其中三個屬性只是簡單的值容器。有一種回調方法,只要修改了這些回調方法,在該方法中,代碼只是將第四個屬性設置爲三者之和。
INotifyPropertyChanged
接口只有一個成員,即PropertyChanged
事件。在處理MVVM樣式的代碼時,您會發現有一個實際實現此事件的基類以及像上面所示的_UpdateField()
方法這樣的幫助方法,屬性設置器可調用它來處理每個此類屬性所需的重複邏輯。在上面的示例中,爲了簡化示例,我將所有這些邏輯合併到一個類中,但是您可能希望保留一個合適的基類(我和其他許多人在Visual Studio中配置了片段輕鬆地將此樣板代碼插入到項目中)。
有了這麼定義的視圖模型中,XAML被簡化成這個樣子:
<Window x:Class="TestSO45170241SliderExample.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:l="clr-namespace:TestSO45170241SliderExample"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<Grid>
<Label Content="Sample Selection" FontSize="16" FontStyle="Italic" FontWeight="Bold"
HorizontalAlignment="Left" Margin="0,-30,0,0" VerticalAlignment="Top"/>
<Label Content="Patient Samples (max 64)" HorizontalAlignment="Left" Margin="10,10,0,0"
VerticalAlignment="Top" FontStyle="Italic"/>
<Slider HorizontalAlignment="Left" Margin="181,14,0,0"
VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="64"
Value="{Binding SampleCount}" IsSnapToTickEnabled="True"/>
<TextBlock HorizontalAlignment="Left" Margin="165,16,0,0"
TextWrapping="Wrap" Text="{Binding SampleCount}" VerticalAlignment="Top"/>
<Label Content="Calibrators (max 7)" HorizontalAlignment="Left" Margin="10,36,0,0"
VerticalAlignment="Top" FontStyle="Italic"/>
<Slider HorizontalAlignment="Left" Margin="181,40,0,0"
VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="7"
Value="{Binding CalibratorCount}" IsSnapToTickEnabled="True"/>
<TextBlock HorizontalAlignment="Left" Margin="165,42,0,0"
TextWrapping="Wrap" Text="{Binding CalibratorCount}" VerticalAlignment="Top"/>
<Label Content="Control Samples (max 4)" HorizontalAlignment="Left" Margin="10,62,0,0"
VerticalAlignment="Top" FontStyle="Italic"/>
<Slider HorizontalAlignment="Left" Margin="181,66,0,0"
VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="4"
Value="{Binding ControlCount}" IsSnapToTickEnabled="True"/>
<TextBlock HorizontalAlignment="Left" Margin="165,68,0,0"
TextWrapping="Wrap" Text="{Binding ControlCount}" VerticalAlignment="Top"/>
<Label Content="Total Sample Preparations Selected:" HorizontalAlignment="Left"
Margin="10,105,0,0" VerticalAlignment="Top" FontWeight="Bold" FontStyle="Italic"/>
<TextBlock HorizontalAlignment="Left" Margin="225,110,0,0"
FontWeight="Bold" FontStyle="Italic" Text="{Binding TotalPrepCount}"
VerticalAlignment="Top"/>
</Grid>
</Window>
(旁白:除了改變支持MVVM的做法,我沒有修改你的基本UI聲明我同意另外一個你想開始熟悉的東西是如何利用WPF的各種佈局容器和元素樣式特性,但我認爲在這裏介紹這些只會混淆事項。原始的用戶界面大部分完好無損,您可以專注於那些與原來不同的東西,幫助您瞭解更多信息呃數據綁定方面不用分心。)
在此實現,有沒有代碼任何添加到MainWindow.xaml.cs
文件。該文件中的所有內容都是構造函數中默認的InitializeComponent()
調用,由Visual Studio的WPF項目模板提供。
另一方面,在XAML中,我用直接綁定到Slider.Value
屬性的綁定替換了事件處理函數訂閱。還請注意,TextBlock.Text
屬性也綁定到相同的屬性。通過這種方式,WPF完成了將滑塊值存儲在業務邏輯專用數據結構中的所有重要工作,然後在視圖中的文本字段中重新顯示這些值。您會注意到WPF甚至處理了各種數據類型之間的轉換:視圖模型存儲值爲int
,但滑塊使用double
,文本塊當然使用string
。
當然,TotalPrepCount
字段也綁定到感興趣的TextBlock.Text
屬性以供顯示。
最後,我會注意到,即使在您的簡單示例中,您還有其他可能需要應用此數據綁定方法的位置。特別是,每個滑塊都有最大值,這些值被硬編碼到視圖中。 MVVM的觀點是爲了不需要封裝關於業務邏輯的任何知識。這將包括不必知道允許值的全部範圍(*)。
因此,您的視圖模型也可能有一個MaxSampleCount
屬性,該屬性綁定到Slider.Maximum
屬性和Label.Content
屬性。在後一種情況下,您可以使用Binding.StringFormat
屬性將值合併到文本中。例如:
<Label Content="{Binding MaxSampleCount, StringFormat=Patient Samples (max {0})" ... />
我會欣然承認,當我第一次開始嘗試使用WPF,之後使用UI API,如本地的Win32控件年復一年,MFC,Windows窗體,Java的API的(鞦韆,AWT, SWT),甚至Mac OS的Cocoa框架(它使用一種數據綁定形式,而不是像XAML這樣的聲明式UI),我努力改變思維方式,以避免使用所有其他API中使用的程序方法來適應混合聲明性和程序性方法與WPF一起使用。
但是我試圖嚴格從MSDN提供的文檔中學習它,它最好是密集編寫的,通常只是普通難以遵循,並且在很多情況下完全沒用。如果有人剛纔向我展示了一個例子,就像我上面展示的那樣(他們確實存在,甚至在那時候......我只是不知道它們在哪裏),但我會後來看到基本MVVM方法有多容易,如果遵循這種方法,可以更快地寫出WPF程序。
我希望以上內容有助於您瞭解WPF的生產用途,並以易於理解的方式向您展示基礎知識。
嘗試使用容器('Grid' with row/columns,'StackPanel'等)而不是'Margin'來創建佈局。至於這個問題,將計算移動到所有事件處理程序調用的方法中,在那裏設置文本總和。更簡單的方法是開始使用綁定+ MVVM,然後創建屬性'public double Sum => ...'並在每個用於計算總和的屬性中上升通知('INotifyPropertyChanged')是正確的方法。 – Sinatr
@Sinatr感謝您的回覆!我會盡快研究這一點,如果我成功了,我會讓你知道! – WSU
binding + mvvm更簡單的方法?你是認真的?多數民衆贊成在一個開始的人更難。這可能是「專業」的方式來做他想做的事情,但它當然不是最簡單的,他可以創建一種方法,在任何滑塊更改時更新文本,非常簡單。 –