刷新 WPF 中子控件的 DataContext
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19267924/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Refresh DataContext for child controls in WPF
提问by wd113
We have a client-server application which has the requirement of building the View dynamically. The server will send the XAML string together with the data (Dctionary< string, string>) to the client, which will then build the View from received Xaml string and bind the data to the View.
我们有一个客户端 - 服务器应用程序,它需要动态构建视图。服务器会将 XAML 字符串与数据 (Dctionary<string, string>) 一起发送到客户端,然后客户端将从接收到的 Xaml 字符串构建视图并将数据绑定到视图。
Here is the sample XAML string:
这是示例 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>
The data would look like:
数据看起来像:
new Dictionary<string, string>
{
{"ID_Id", "1"},
{"ID_Name", "John"}
};
The client will build the View by using XamlReader.Load(), and create a Window to host it as the Content. The client also assigns the received data to Window.DataContext
客户端将使用 XamlReader.Load() 构建视图,并创建一个窗口以将其作为内容托管。客户端还将接收到的数据赋值给Window.DataContext
window.DataContext = dictionaryData;
As the two TextBox's inherit the DataContext from Window, the Text property binds to the Dictionary. The binding converter "fieldBindingConverter" fetches the correct value out of the dictionary by using ConverterParameter which has the key.
由于两个 TextBox 从 Window 继承 DataContext,因此 Text 属性绑定到 Dictionary。绑定转换器“fieldBindingConverter”使用具有键的 ConverterParameter 从字典中获取正确的值。
So the Two TextBox's will disply "1" and "John" correspondingly when the View is first built.
因此,当首次构建视图时,两个 TextBox 将相应地显示“1”和“John”。
The problem arises when a new data arrives at client side
当新数据到达客户端时出现问题
new Dictionary<string, string>
{
{"ID_Id", "2"},
{"ID_Name", "Peter"}
};
By resetting the DataContext of the hosting Window will not make the binding on the TextBox refresh itself
通过重置宿主窗口的 DataContext 不会使 TextBox 上的绑定刷新自身
window.DataContext = newDictionaryData;
In fact the DataContext of the TextBox still cache the old data value.
实际上 TextBox 的 DataContext 仍然缓存旧的数据值。
It seems that TextBox only takes a copy of its parent DataContext when it's first initialized, and then only works with that local copy afterwards.
似乎 TextBox 在第一次初始化时只获取其父 DataContext 的副本,然后只使用该本地副本。
It also appears that it not easy to have a ViewModel and implement INotifyPropertyChanged in this scenario, as the key "ID_XX" can vary among different Views and it;s hard to have a Model class defined for this dynamic nature (I could be wrong).
在这种情况下,拥有 ViewModel 并实现 INotifyPropertyChanged 似乎并不容易,因为键“ID_XX”在不同的视图之间可能有所不同,并且很难为这种动态性质定义模型类(我可能是错的) .
It does work correctly if a new hosting Window is created (and DataContext is set) every time a new data arrives, because the DataContext of all TextBox's will have the new data derived for the new hosting window.
如果每次新数据到达时都创建一个新的托管窗口(并设置 DataContext),它确实可以正常工作,因为所有 TextBox 的 DataContext 都将具有为新托管窗口派生的新数据。
Does anyone know how to get the TextBox "refresh" its DataContext to take the new one set on the parent Window and "refresh" the binding?
有谁知道如何让 TextBox“刷新”它的 DataContext 以在父窗口上设置新的并“刷新”绑定?
采纳答案by wd113
I've solved the problem by making the TextBox binding to the parent hosting Window's DataContext.
我通过将 TextBox 绑定到父托管 Window 的 DataContext 解决了这个问题。
Thanks for everyone's comments.
谢谢大家的意见。
回答by Sheridan
In WPF, we don't generally set the DataContextof a Windowto one data type object like this... however it ispossible. Instead, we normally create a specific class that contains all of the properties required to be displayed and as you alluded to, implements the INotifyPropertyChangedinterface. In your case, we would have a property of type Staffthat we could bind to in the UI:
在 WPF 中,我们通常不会像这样DataContext将 a设置Window为一种数据类型对象……但是这是可能的。相反,我们通常会创建一个特定的类,其中包含需要显示的所有属性,并且正如您所提到的,实现了INotifyPropertyChanged接口。在您的情况下,我们将有一个Staff可以绑定到 UI类型的属性:
public string Staff
{
get { return staff; }
set { staff = value; NotifyPropertyChanged("Staff"); }
}
Then in XAML:
然后在 XAML 中:
<Window>
<StackPanel>
<TextBox Text="{Binding Staff.Id}"/>
<TextBox Text="{Binding Staff.Name}"/>
</StackPanel>
</Window>
In this small example, this is not strictly necessary, but in larger projects, it is likely that there would be other data to display as well. If you set the Window.DataContextto just one instance of your Staffdata type, then you would find it tricky to display other data, such as a collection of Staffobjects. Likewise, it is better to update the Staffproperty, which will inform the interface to update the UI, rather than the DataContextwhich will not update the UI as it is not 'connected' to the interface.
在这个小例子中,这不是绝对必要的,但在较大的项目中,很可能还有其他数据要显示。如果Window.DataContext仅将 设置为Staff数据类型的一个实例,那么您会发现显示其他数据(例如Staff对象集合)很棘手。同样,最好更新Staff属性,它会通知界面更新 UI,而不是DataContext不会更新 UI,因为它没有“连接”到界面。
It is the notification of property changes through the INotifyPropertyChangedinterface that updates, or 'refreshes as you call it, the values in the UI controls when property changes are made. So your answer is to implement this interface and make sure that you call the INotifyPropertyChanged.PropertyChangedEventHandlerwhen property value changes are made.
它是通过INotifyPropertyChanged界面的属性更改通知,在进行属性更改时更新或“刷新”UI 控件中的值。所以你的答案是实现这个接口并确保你调用INotifyPropertyChanged.PropertyChangedEventHandlerwhen 属性值更改。
UPDATE >>>
更新 >>>
Wow! You really aren't listening, are you? In WPF, we don't 'refresh' the DataContext, the INotifyPropertyChangedinterface 'refreshes' the UI once properties have been changed. This is one possible way that you could fix this problem:
哇!你真的没有在听,是吗?在 WPF 中,我们不会“刷新” DataContext,INotifyPropertyChanged一旦属性更改,界面就会“刷新”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; }
...
You could then fill your CustomObjectlike this:
然后你可以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>
Now you couldargue that you can't do this for this reason, or you don't want to do that for that reason, but at the end of the day, you have to do something like this to solve your problem.
现在你可以争辩说你不能因为这个原因这样做,或者你不想这样做,但在一天结束时,你必须做这样的事情来解决你的问题。
回答by ouflak
You might consider creating a custom ObservableDictionary < T , U > class inherited from the ObservalbeCollection class. I've done this, and though it's been some work to get the kinks out, it's become one of my most prized custom classes. Some brief bits of code as a suggestion:
您可以考虑创建一个从 ObservalbeCollection 类继承的自定义 ObservableDictionary < T , U > 类。我已经完成了这项工作,尽管解决这些问题需要付出一些努力,但它已成为我最珍贵的自定义类之一。一些简短的代码作为建议:
/// <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<TKey,TValue>
/// 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);
}
}
}
}
That's the basic class. An example method in the class:
那是基础课。类中的示例方法:
/// <summary> Adds a key/value pair to the
/// System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>
/// if the key does not already exist, or updates a key/value pair in the
/// System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>
/// 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;
}
And don't forget the enumerators:
并且不要忘记枚举器:
/// <summary> Returns an enumerator that iterates through the
/// ObservableExtendedCollection<TValue>. </summary>
/// <returns> An enumerator for the
/// underlying ObservableExtendedCollection<TKey,TValue>. </returns>
public new IEnumerator<TValue> GetEnumerator()
{
return (IEnumerator<TValue>)base.GetEnumerator();
}
/// <summary> Returns an enumerator that iterates through the
/// System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>. </summary>
/// <returns> An enumerator for the
/// System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>. </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();
}
Now obviously I've left out some implementation as this is originally derived from my attempt (mostly successful) to make a readonly observable dictionary. But the basics do work. Hope this is of some use.
现在显然我已经遗漏了一些实现,因为这最初源自我尝试(大部分成功)制作只读可观察字典。但基础知识确实有效。希望这有点用。

