2017-07-18 21 views
2

我目前正在使用用戶界面,用戶可以通過三個單獨的滑塊選擇三個值。我有在滑塊的前面有三個的TextBlocks指示特定滑塊的當前值:將三個滑塊值組合成一個Texblock文本/標籤內容?

在的.xaml:

<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 x:Name="SampleAmountSlider" HorizontalAlignment="Left" Margin="181,14,0,0" VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="64" ValueChanged="SampleAmountSlider_ValueChanged" IsSnapToTickEnabled="True"/> 
<TextBlock x:Name="SampleSliderValue" HorizontalAlignment="Left" Margin="165,16,0,0" TextWrapping="Wrap" Text="0" VerticalAlignment="Top"/> 
<Label Content="Calibrators (max 7)" HorizontalAlignment="Left" Margin="10,36,0,0" VerticalAlignment="Top" FontStyle="Italic"/> 
<Slider x:Name="CalAmountSlider" HorizontalAlignment="Left" Margin="181,40,0,0" VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="7" ValueChanged="CalAmountSlider_ValueChanged" IsSnapToTickEnabled="True"/> 
<TextBlock x:Name="CalSliderValue" HorizontalAlignment="Left" Margin="165,42,0,0" TextWrapping="Wrap" Text="0" VerticalAlignment="Top"/> 
<Label Content="Control Samples (max 4)" HorizontalAlignment="Left" Margin="10,62,0,0" VerticalAlignment="Top" FontStyle="Italic"/> 
<Slider x:Name="ControlAmountSlider" HorizontalAlignment="Left" Margin="181,66,0,0" VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="4" ValueChanged="ControlAmountSlider_ValueChanged" IsSnapToTickEnabled="True"/> 
<TextBlock x:Name="ControlSliderValue" HorizontalAlignment="Left" Margin="165,68,0,0" TextWrapping="Wrap" Text="0" VerticalAlignment="Top"/> 
<Label Content="Total Sample Preparations Selected:" HorizontalAlignment="Left" Margin="10,105,0,0" VerticalAlignment="Top" FontWeight="Bold" FontStyle="Italic"/> 
<TextBlock x:Name="TotalPrepValue" HorizontalAlignment="Left" Margin="225,110,0,0" FontWeight="Bold" FontStyle="Italic" Text="0" VerticalAlignment="Top"/> 

在.xaml.cs:

private void SampleAmountSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) 
    { 
     SampleSliderValue.Text = Math.Round(e.NewValue, 0).ToString(); 
    } 

private void CalAmountSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) 
    { 
     CalSliderValue.Text = Math.Round(e.NewValue, 0).ToString(); 
    } 

private void ControlAmountSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) 
    { 
     ControlSliderValue.Text = Math.Round(e.NewValue, 0).ToString(); 
    } 

我想要的是最後一個文本塊(名爲TotalPrepValue),用於包含患者樣本數量,校準器數量和對照樣本數量的總和。

我不知道我是否問了很多問題或者問題不清楚(如果是這樣,請讓我知道,我會盡快回答)。 事情是,我是一個非常缺乏經驗的程序員,但願意學習! 預先感謝您的幫助!

+1

嘗試使用容器('Grid' with row/columns,'StackPanel'等)而不是'Margin'來創建佈局。至於這個問題,將計算移動到所有事件處理程序調用的方法中,在那裏設置文本總和。更簡單的方法是開始使用綁定+ MVVM,然後創建屬性'public double Sum => ...'並在每個用於計算總和的屬性中上升通知('INotifyPropertyChanged')是正確的方法。 – Sinatr

+0

@Sinatr感謝您的回覆!我會盡快研究這一點,如果我成功了,我會讓你知道! – WSU

+0

binding + mvvm更簡單的方法?你是認真的?多數民衆贊成在一個開始的人更難。這可能是「專業」的方式來做他想做的事情,但它當然不是最簡單的,他可以創建一種方法,在任何滑塊更改時更新文本,非常簡單。 –

回答

1

像其他人所建議的那樣,您可以在此方案中使用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的生產用途,並以易於理解的方式向您展示基礎知識。

+0

非常感謝!這一切似乎工作!我要測試上面描述的另一個解決方案,以及我是否可以學習更多。再次感謝您,我會盡快研究整個MVVM方法。 – WSU

2

個人而言,這是我會做的。然而,如果你喜歡它,那麼也請去upvote @JerryNixon的answer here,因爲它基本上只是使用滑塊而不是他的例子的重構因素,他值得更多的讚揚。通常情況下,我只是直接指出了它,但我知道如何開始使用某些東西,並且更清晰的PoC可能更有用。

總之,這裏亞去,一個漂亮的圖片開始...

enter image description here

的XAML;

<Window.Resources> 
    <local:SumConverter x:Key="MySumConverter" /> 
</Window.Resources> 
<Grid> 


    <StackPanel VerticalAlignment="Center"> 
     <StackPanel.Resources>    
      <Style TargetType="Slider"> 
       <Setter Property="Margin" Value="10"/> 
       <Setter Property="Width" Value="200"/> 
       <Setter Property="Minimum" Value="0"/> 
       <Setter Property="Maximum" Value="100"/> 
      </Style> 
     </StackPanel.Resources> 

     <Slider x:Name="Slider1"></Slider> 
     <Slider x:Name="Slider2"></Slider> 
     <Slider x:Name="Slider3"></Slider> 

     <TextBlock HorizontalAlignment="Center" TextAlignment="Center"> 
      <Run Text="{Binding Value, ElementName=Slider1}"/> 
      <LineBreak/><LineBreak/> 
      <Run Text="{Binding Value, ElementName=Slider2}"/> 
      <LineBreak/><LineBreak/> 
      <Run Text="{Binding Value, ElementName=Slider3}"/> 
      <LineBreak/> 
      <Run Text="______________________"/> 
      <LineBreak/><LineBreak/> 
      <Run> 
       <Run.Text> 
        <MultiBinding Converter="{StaticResource MySumConverter}" 
            StringFormat="{}{0:C}" 
            FallbackValue="Error" TargetNullValue="Null"> 
         <Binding Path="Value" ElementName="Slider1"/> 
         <Binding Path="Value" ElementName="Slider2"/> 
         <Binding Path="Value" ElementName="Slider3"/> 
        </MultiBinding> 
       </Run.Text> 
      </Run> 
     </TextBlock>    

    </StackPanel> 


</Grid> 

The Converter;

public class SumConverter : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, 
     object parameter, System.Globalization.CultureInfo culture) 
    { 
     double _Sum = 0; 
     if (values == null) 
      return _Sum; 
     foreach (var item in values) 
     { 
      double _Value; 
      if (double.TryParse(item.ToString(), out _Value)) 
       _Sum += _Value; 
     } 
     return _Sum; 
    } 

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

希望這會有所幫助,歡呼聲。

+1

乾淨的代碼 – tabby

+0

局部變量有點奇怪的命名,下劃線+ Pascal? Camel更標準(查看MSDN示例,例如[here](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions))。 – Sinatr

+0

非常感謝!我已經給出了兩個解決方案,兩者都非常有用!尼斯形象btw;) – WSU