2016-03-06 86 views
0

WPF n00bie在這裏,試圖讓他的用戶界面正常工作。WPF〜與綁定和INotifyPropertyChanged故障

所以我做了這個測試的例子。綁定到HeaderText1的文本塊在應用程序啓動時正確更改,但綁定到HeaderText2的文本塊在單擊按鈕後不會更新。

我在做什麼錯?提前致謝!!

<Window x:Class="DataBinding.DataContextSample" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="DataContextSample" Height="142.596" Width="310"> 
    <StackPanel Margin="15"> 
     <WrapPanel> 
      <TextBlock Text="Window title: " /> 
      <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" /> 
      <Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button> 
     </WrapPanel> 
     <TextBlock Text="{Binding Path=DataContext.HeaderText}"></TextBlock> 
     <TextBlock Text="{Binding Path=DataContext.HeaderText2}"></TextBlock> 
    </StackPanel> 
</Window> 

主窗口類:

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


namespace DataBinding 
{ 
    public partial class DataContextSample : Window 
    { 
     public string HeaderText { set; get; } 



     public DataContextSample() 
     { 
      HeaderText = "YES"; 


      InitializeComponent(); 
      this.DataContext = this; 

     } 

     private void btnUpdateSource_Click(object sender, RoutedEventArgs e) 
     { 

      BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty); 
      binding.UpdateSource(); 

      Source source = new Source(); 
      source.HeaderText2 = "YES2"; 
     } 
    } 
} 

而且INotifyPropertyChanged的類

using System.ComponentModel; 

namespace DataBinding 
{ 
    public class Source : INotifyPropertyChanged 
    { 
     public string HeaderText2 { set; get; } 

     public event PropertyChangedEventHandler PropertyChanged; 


     protected virtual void OnPropertyChanged(string propertyName) 
     { 
      PropertyChangedEventHandler handler = this.PropertyChanged; 
      if (handler != null) 
      { 
       var e = new PropertyChangedEventArgs(propertyName); 
       handler(this, e); 
      } 
     } 

    } 
} 

回答

2

首先,你正在做的很多事情是錯誤的。

你不應該使用窗口作爲自己的datacontext,你應該有一個你設置的視圖模型。

您不應該在視圖中使用事件處理程序來操作視圖模型。你應該將按鈕綁定到一個命令。

您的源似乎是「視圖模型」,考慮將其重命名爲MainWindowViewModel(爲清晰起見),然後執行此操作。

public class MainWindowViewModel : INotifyPropertyChanged 
{ 
    private string headerText; 
    private string headerText2; 
    private ICommand updateHeaderText2; 

    public string HeaderText 
    { 
     set 
     { 
      return this.headerText; 
     } 
     get 
     { 
      this.headerText = value; 

      // Actually raise the event when property changes 
      this.OnPropertyChanged("HeaderText"); 
     } 
    } 

    public string HeaderText2 
    { 
     set 
     { 
      return this.headerText2; 
     } 
     get 
     { 
      this.headerText2 = value; 

      // Actually raise the event when property changes 
      this.OnPropertyChanged("HeaderText2"); 
     } 
    } 

    public ICommand UpdateHeaderText2 
    { 
     get 
     { 
      // Google some implementation for ICommand and add the MyCommand class to your solution. 
      return new MyCommand (() => this.HeaderText2 = "YES2"); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = this.PropertyChanged; 
     if (handler != null) 
     { 
      var e = new PropertyChangedEventArgs(propertyName); 
      handler(this, e); 
     } 
    } 
} 

並將此viewmodel設置爲您的窗口的datacontext。

this.DataContext = new MainWindowViewModel(); 

,然後在XAML中,你應該綁定到視圖模型本身

<Window x:Class="DataBinding.DataContextSample" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="DataContextSample" Height="142.596" Width="310"> 
    <StackPanel Margin="15"> 
     <WrapPanel> 
      <TextBlock Text="Window title: " /> 
      <!-- Not sure what this binding is? --> 
      <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" /> 
      <Button Name="btnUpdateSource" Command="{Binding UpdateHeaderText2}" Margin="5,0" Padding="5,0">*</Button> 
     </WrapPanel> 
     <TextBlock Text="{Binding HeaderText}"></TextBlock> 
     <TextBlock Text="{Binding HeaderText2}"></TextBlock> 
    </StackPanel> 
</Window> 
+0

這是OPs問題的正確答案。 –

+0

感謝您的回答!作爲後續問題,如果我要爲底層邏輯建立模型,我將如何處理按鈕的命令?將它從視圖發送到視圖模型,然後發送到模型?這個例子看起來像什麼? – ThomQ

+0

這是包含在示例中,該命令的執行不是,應該可以通過google輕鬆訪問。 –

1

您設置DataContextthis(窗口)。您在DataContext中沒有名爲HeaderText2的媒體資源,因此第二個綁定無效。

我會做到這一點(不改變你的代碼太多了,在現實中我會做一個適當的MVVM方法):

public partial class DataContextSample : Window 
{ 
    public Source Source { get; set; } 
    public string HeaderText { set; get; } 

    public MainWindow() 
    { 
     InitializeComponent(); 

     HeaderText = "YES"; 
     Source = new Source { HeaderText2 = "YES" }; 
     DataContext = this; 
    } 

    private void btnUpdateSource_Click(object sender, RoutedEventArgs e) 
    { 
     BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty); 
     if (binding != null) 
     { 
      binding.UpdateSource(); 
     } 

     Source.HeaderText2 = "YES2"; 
    } 
} 

我增加了一個名爲Source新的屬性,它是Source類型。在構造函數中將其初始HeaderText2設置爲相同的"YES",並在按鈕中單擊更改爲"YES2"

你必須改變你的Source類爲好,以實際通知有關的變化:

public class Source : INotifyPropertyChanged 
{ 
    private string _headerText2; 

    public string HeaderText2 
    { 
     get { return _headerText2; } 
     set 
     { 
      _headerText2 = value; 
      OnPropertyChanged("HeaderText2"); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = this.PropertyChanged; 
     if (handler != null) 
     { 
      var e = new PropertyChangedEventArgs(propertyName); 
      handler(this, e); 
     } 
    } 
} 

,然後在XAML:

<StackPanel Margin="15"> 
    <WrapPanel> 
     <TextBlock Text="Window title: " /> 
     <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" /> 
     <Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button> 
    </WrapPanel> 
    <TextBlock Text="{Binding Path=HeaderText}"></TextBlock> 
    <TextBlock Text="{Binding Path=Source.HeaderText2}"></TextBlock> 
</StackPanel> 
+0

謝謝,這個作品,你的編輯很有道理! – ThomQ

+0

但是關於適當的MVVM方法;所以我得到了MainWindow類,它是視圖,Source類是ViewModel。模型類應該是它背後的邏輯,對嗎?所以,當我點擊按鈕時,線程從視圖的btnUpdateSource_Click中產生,它應該通過源發送所有內容到第三個類,然後處理所有內容,並且如果需要,更改ViewModel中的屬性,是否正確?那麼我會在哪裏移動DataContext呢?你可以舉一個MVVM方法的小例子嗎? – ThomQ

1

那麼有你的代碼的幾個問題。 首先,你永遠不會將你的「Source」分配給datacontext,所以你的第二個TextBlock無法找到「HeaderText2」的值。

如果您要將「源」分配給文本塊datacontext,那麼我們可以獲取「HeaderText2」的值。考慮下面

<Window x:Class="DataBinding.DataContextSample" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="DataContextSample" Height="142.596" Width="310"> 
    <StackPanel Margin="15"> 
     <WrapPanel> 
      <TextBlock Text="Window title: " /> 
      <TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" /> 
      <Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button> 
     </WrapPanel> 
     <TextBlock Text="{Binding Path=HeaderText}"></TextBlock> 
     <TextBlock Name="TextBlock2" Text="{Binding Path=HeaderText2}"></TextBlock> 
    </StackPanel> 
</Window> 

我們已經給你的第二個文本塊的名稱代碼「TextBlock2」,也從你的綁定去掉了「DataContext的」雙組分。

然後,我們已經將按鈕事件的「源」對象的創建從窗口構造函數移動到窗口構造函數中(當我們想要做的所有事情都是更新屬性時,無需每次點擊按鈕就創建一個新窗體)

public partial class DataContextSample : Window 
{ 
    public string HeaderText { set; get; } 
    private Source source { get; set; } 

    public DataContextSample() 
    { 
     ... 

     source = new Source(); 
     TextBlock2.DataContext = source; 

     ... 
    } 

    ... 
} 

然後在您的按鈕單擊事件中,我們爲您的數據綁定屬性賦值「YES2」。

private void btnUpdateSource_Click(object sender, RoutedEventArgs e) 
{ 
    ... 

    source.HeaderText2 = "YES2"; 
} 

然而,還有一個細節。你的類「源」確實實現了「INotifyPropertyChanged」,但它從來沒有「使用」它。我的意思是,當你給你的屬性賦值「HeaderText2」時,你永遠不會「通知」UI有什麼改變,因此UI將不會獲取新的值。考慮下面的代碼:

public class Source : INotifyPropertyChanged 
{   
    public string HeaderText2 { set 
     { 
      headerText2 = value; 
      OnPropertyChanged("HeaderText2"); 
     } 
     get 
     { 
      return headerText2; 
     } 
    } 
    string headerText2; 

    ... 
} 

那麼讓我們來看看我們已經與物業「HeaderText2」來完成。每當「HeaderText2」獲得一個賦值時,它將首先將該值保存在一個privat屬性中(以便我們稍後可以讀取它)。但除此之外,我們還使用我們的Propertys名稱調用「OnPropertyChanged」方法。該方法將依次檢查是否有人正在「傾聽」我們的「PropertyChanged」事件(並且因爲我們有關於當前對象的數據綁定,某人正在監聽),並創建一個新事件。

現在我們已經爲您的文本塊指定了一個帶有「HeaderText2」路徑的數據源,當我們更新數據源中的「HeaderText2」時,我們會通知所有偵聽器,並且我們正在更新按鈕單擊事件中的「HeaderText2」。

快樂編碼!

+0

非常感謝您的詳細解釋。如果我以真正的MVVM形式來做這個例子,我需要第三個類,對嗎?然後在那裏做所有的邏輯,對吧?那將是什麼樣子? – ThomQ