2013-10-09 70 views
2

我们有一个客户端 - 服务器应用程序,它具有动态构建View的要求。服务器会将XAML字符串与数据(Dctionary < string,string>)一起发送到客户端,然后客户端将根据收到的Xaml字符串构建View并将数据绑定到View。刷新WPF中子控件的DataContext

下面是示例XAML字符串:

 <StackPanel> 
     <TextBox> 
      <TextBox.Text> 
       <Binding RelativeSource="{{RelativeSource Self}}" Path="DataContext" 
         Converter="{{StaticResource fieldBindingConverter}}" ConverterParameter="ID_Id" 
         UpdateSourceTrigger="PropertyChanged"> 
       </Binding> 
      </TextBox.Text> 
     </TextBox> 

     <TextBox> 
      <TextBox.Text> 
       <Binding RelativeSource="{{RelativeSource Self}}" Path="DataContext" 
         Converter="{{StaticResource fieldBindingConverter}}" ConverterParameter="ID_Name" 
         UpdateSourceTrigger="PropertyChanged"> 
       </Binding> 
      </TextBox.Text> 
     </TextBox>   
    </StackPanel> 

的数据将是这样的:

new Dictionary<string, string> 
    { 
     {"ID_Id", "1"}, 
     {"ID_Name", "John"} 
    }; 

客户端将通过XamlReader.Load()构建视图,并创建一个窗口将其作为内容托管。客户端还接收到的数据分配给Window.DataContext

window.DataContext = dictionaryData; 

由于从窗口两个TextBox的继承DataContext的,Text属性绑定到词典。 绑定转换器“fieldBindingConverter”通过使用具有密钥的ConverterParameter从字典中提取正确的值。

因此,当第一次构建View时,两个TextBox将相应地显示“1”和“John”。

的问题,当一个新数据到达客户端

new Dictionary<string, string> 
    { 
     {"ID_Id", "2"}, 
     {"ID_Name", "Peter"} 
    }; 

通过重置托管窗口的DataContext的不会让出现在文本框的结合刷新自身

window.DataContext = newDictionaryData; 

事实上, TextBox的DataContext仍旧缓存旧的数据值。

似乎TextBox只在第一次初始化时接受其父DataContext的一个副本,然后仅在此后使用该本地副本。

在这种情况下,看起来并不容易有一个ViewModel并实现INotifyPropertyChanged,因为关键“ID_XX”可以在不同视图中变化,并且它很难为这个动态性质定义Model类可能是错误的)。

如果每次新数据到达时都创建新的托管窗口(并设置DataContext),它会正常工作,因为所有TextBox的DataContext都将为新的托管窗口派生新数据。

有谁知道如何让文本框“刷新”其DataContext采取新的父窗口设置和“刷新”绑定?

+0

如何以及何时更改窗口的DataContext? – Nitin

+0

在新数据到达时由事件触发。 – wd113

回答

-1

我已经通过将TextBox绑定到父主机Window的DataContext来解决问题。

感谢大家的意见。

2

在WPF中,我们一般不设置Window到一个数据类型对象的DataContext这样的...但它可能。相反,我们通常会创建一个包含所有需要显示的属性的特定类,并且如您所暗示的那样,实现了INotifyPropertyChanged接口。在你的情况,我们将有我们可以绑定到UI Staff类型的属性:

public string Staff 
{ 
    get { return staff; } 
    set { staff = value; NotifyPropertyChanged("Staff"); } 
} 

然后在XAML:

<Window> 
    <StackPanel> 
     <TextBox Text="{Binding Staff.Id}"/> 
     <TextBox Text="{Binding Staff.Name}"/> 
    </StackPanel> 
</Window> 

在这个小例子,这不是绝对必要的,但在较大的项目中,很可能还会显示其他数据。如果将Window.DataContext设置为您的Staff数据类型的一个实例,那么显示其他数据(例如一组Staff对象)会很棘手。同样,最好更新Staff属性,它将通知接口更新UI,而不是DataContext,因为它没有“连接”到接口,所以不会更新UI。

它是通过INotifyPropertyChanged接口更新属性更改的通知,或者是在您调用它时刷新UI控件中的属性更改时的值。所以你的答案是实现这个接口,并确保在属性值更改时调用INotifyPropertyChanged.PropertyChangedEventHandler

更新>>>

哇!你真的不听,是吗?在WPF中,我们不'刷新'DataContextINotifyPropertyChanged界面在属性更改后'刷新'UI。这是你能解决这个问题的一种可能的方式:

public class CustomObject 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    ... 
} 

... 

Dictionary<string, string> data = new Dictionary<string, string> 
{ 
    {"ID_Id", "1"}, 
    {"ID_Name", "John"} 
    ... 
}; 

... 

// Implement the `INotifyPropertyChanged` interface on this property 
public ObservableCollection<CustomObject> CustomObjects { get; set; } 

... 

然后,您可以填写您的CustomObject这样的:

CustomObjects = new ObservableCollection<CustomObject>(); 
CustomObject customObject = new CustomObject(); 
foreach (KeyValuePair<string, string> entry in data) 
{ 
    if (entry.Key == "ID_Id") // Assuming this always comes first 
    { 
     customObject = new CustomObject(); 
     customObject.Id = int.Parse(entry.Value); 
    } 
    ... 
    else if (entry.Key == "ID_Name") // Assuming this always comes lasst 
    { 
     customObject.Name = entry.Value; 
     customObjects.Add(customObject); 
    } 
} 

... 

<ListBox ItemsSource="{Binding CustomObjects}"> 
    <ListBox.ItemTemplate> 
     <DataTemplate DataType="{x:Type DataTypes:CustomObject}"> 
      <StackPanel> 
       <TextBox Text="{Binding Id}"/> 
       ... 
       <TextBox Text="{Binding Name}"/> 
      </StackPanel> 
     </DataTemplate DataType="{x:Type DataTypes:CustomObject}"> 
    </ListBox.ItemTemplate> 
</Window> 

现在你可以争辩你不能为此做理由,或者因为这个原因你不想这样做,但是在一天结束时,你必须这样做来解决你的问题。

+0

感谢您的回复。我明白INotifyPropertyChanged的好处。正是在这个特定的场景中,我们无法使用它,并且怀疑重置DataContext是否会执行相同的技巧。 – wd113

+0

对不起,但不会。为什么不能使用INotifyPropertyChanged接口? – Sheridan

+0

数据是Dictionary ,每个TextBox绑定到字典中的一个keyValuePair的值。数据是动态生成的,并且没有Model类。所以我们不能使用INPC。我只使用Staff类来解释DataContext的差异。这可能会导致混淆。对于那个很抱歉。 – wd113

1

您可能会考虑创建一个自定义的ObservableDictionary < T,U>继承自ObservalbeCollection类的类。我已经完成了这项工作,虽然为了解决问题已经做了一些工作,但它已经成为我最珍贵的定制课程之一。一些代码简短位作为一个建议:

/// <summary>Dictionary changed event handler</summary> 
/// <param name="sender">The dictionary</param> 
/// <param name="e">The event arguments</param> 
public delegate void NotifyDictionaryChangedEventHandler(object sender, NotifyDictionaryChangedEventArgs e); 

public class CollectionDictionary<TKey, TValue> : ObservableCollection<TValue> 
{ 
    #region Fields 
    private ConcurrentDictionary<TKey, TValue> collectionDictionary = 
     new ConcurrentDictionary<TKey, TValue>(); 
    #endregion 

    /// <summary> 
    /// Initializes a new instance of the CollectionDictionary class 
    /// </summary> 
    public CollectionDictionary() 
    { 
    } 

    /// <summary> 
    /// Initializes a new instance of the CollectionDictionary class 
    /// </summary> 
    /// <param name="collectionDictionary">A dictionary</param> 
    public CollectionDictionary(Dictionary<TKey, TValue> collectionDictionary) 
    { 
     for (int i = 0; i < collectionDictionary.Count; i++) 
     { 
      this.Add(collectionDictionary.Keys.ToList()[i], collectionDictionary.Values.ToList()[i]);     
     } 
    } 

    /// <summary> 
    /// Initializes a new instance of the CollectionDictionary class 
    /// </summary> 
    /// <param name="collectionDictionary">A concurrent dictionary</param> 
    public CollectionDictionary(ConcurrentDictionary<TKey, TValue> collectionDictionary) 
    { 
     this.collectionDictionary = collectionDictionary; 
    } 

    #region Events 
    /// <summary>The dictionary has changed</summary> 
    public event NotifyDictionaryChangedEventHandler DictionaryChanged; 
    #endregion 

    #region Indexers 
    /// <summary> Gets the value associated with the specified key. </summary> 
    /// <param name="key"> The key of the value to get or set. </param> 
    /// <returns> Returns the Value property of the System.Collections.Generic.KeyValuePair&lt;TKey,TValue&gt; 
    /// at the specified index. </returns> 
    public TValue this[TKey key] 
    { 
     get 
     { 
      TValue tValue; 

      if (this.collectionDictionary.TryGetValue(key, out tValue) && (key != null)) 
      { 
       return this.collectionDictionary[key]; 
      } 
      else 
      { 
       return tValue; 
      } 
     } 

     ////set 
     ////{ 
     //// this.collectionDictionary[key] = value; 

     //// string tKey = key.ToString(); 
     //// string tValue = this.collectionDictionary[key].ToString(); 
     //// KeyValuePair<TKey, TValue> genericKeyPair = new KeyValuePair<TKey, TValue>(key, value); 
     //// List<KeyValuePair<TKey, TValue>> keyList = this.collectionDictionary.ToList(); 

     //// for (int i = 0; i < keyList.Count; i++) 
     //// { 
     ////  if (genericKeyPair.Key.ToString() == keyList[i].Key.ToString()) 
     ////  { 
     ////   RemoveAt(i, String.Empty); 
     ////   Insert(i, value.ToString(), String.Empty); 
     ////  } 
     //// } 
     ////} 
    } 

    /// <summary> 
    /// Gets the value associated with the specific index 
    /// </summary> 
    /// <param name="index">The index</param> 
    /// <returns>The value at that index</returns> 
    public new TValue this[int index] 
    { 
     get 
     { 
      if (index > (this.Count - 1)) 
      { 
       return default(TValue); 
      } 
      else 
      { 
       return this.collectionDictionary.ToList()[index].Value; 
      } 
     } 
    } 
    #endregion 

    /// <summary> 
    /// Dictionary has changed. Notify any listeners. 
    /// </summary> 
    /// <param name="e">Evevnt arguments</param> 
    protected virtual void OnDictionaryChanged(NotifyDictionaryChangedEventArgs e) 
    { 
     if (this.DictionaryChanged != null) 
     { 
      this.DictionaryChanged(this, e); 
     } 
    } 

}

这是基本的类。在类的实例方法:

/// <summary> Adds a key/value pair to the 
    /// System.Collections.Concurrent.ConcurrentDictionary&lt;TKey,TValue&gt; 
    /// if the key does not already exist, or updates a key/value pair in the 
    /// System.Collections.Concurrent.ConcurrentDictionary&lt;TKey,TValue&gt; 
    /// if the key already exists. </summary> 
    /// <param name="key"> The key to be added or whose value should be updated </param> 
    /// <param name="addValueFactory">The function used to generate a value for an absent key</param> 
    /// <param name="updateValueFactory">The function used to generate a new value for an 
    /// existing key based on the key's existing value</param> 
    /// <returns> The new value for the key. This will be either be the result of addValueFactory 
    /// (if the key was absent) or the result of updateValueFactory (if the key was 
    /// present). </returns> 
    public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory) 
    { 
     TValue value; 
     value = this.collectionDictionary.AddOrUpdate(key, addValueFactory, updateValueFactory); 

     if (this.collectionDictionary.TryGetValue(key, out value)) 
     { 
      ArrayList valueList = new ArrayList() { value }; 
      ArrayList keyList = new ArrayList() { key }; 
      NotifyDictionaryChangedEventArgs e = new NotifyDictionaryChangedEventArgs(
       NotifyCollectionChangedAction.Add, 
       valueList, 
       keyList); 

      this.Add(value, string.Empty); 
      this.OnDictionaryChanged(e); 
     } 

     return value; 
    } 

而且不要忘了普查员:

 /// <summary> Returns an enumerator that iterates through the 
    /// ObservableExtendedCollection&lt;TValue&gt;. </summary> 
    /// <returns> An enumerator for the 
    /// underlying ObservableExtendedCollection&lt;TKey,TValue&gt;. </returns> 
    public new IEnumerator<TValue> GetEnumerator() 
    { 
     return (IEnumerator<TValue>)base.GetEnumerator(); 
    } 

    /// <summary> Returns an enumerator that iterates through the 
    /// System.Collections.Concurrent.ConcurrentDictionary&lt;TKey,TValue&gt;. </summary> 
    /// <returns> An enumerator for the 
    /// System.Collections.Concurrent.ConcurrentDictionary&lt;TKey,TValue&gt;. </returns> 
    /// <param name="collectionFlag">Flag indicates to return the collection enumerator</param> 
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator(bool collectionFlag = true) 
    { 
     return this.collectionDictionary.GetEnumerator(); 
    } 

现在很明显,我已经离开了一些实现,因为这原本是来自我的努力得到的(大部分是成功的)做一个只读的字典。但基础知识确实有效。希望这有些用处。