我们有一个客户端 - 服务器应用程序,它需要动态构建View。服务器将XAML字符串与数据(Dctionary< string,string>)一起发送到客户端,然后客户端将从接收到的Xaml字符串构建View并将数据绑定到View。
以下是示例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()构建View,并创建一个Window以将其作为Content托管。客户端还将接收的数据分配给Window.DataContext
window.DataContext = dictionaryData;
当两个TextBox从Window继承DataContext时,Text属性绑定到Dictionary。 绑定转换器“fieldBindingConverter”通过使用具有密钥的ConverterParameter从字典中提取正确的值。
因此,当View首次构建时,两个TextBox将相应地显示“1”和“John”。
当新数据到达客户端时出现问题
new Dictionary<string, string>
{
{"ID_Id", "2"},
{"ID_Name", "Peter"}
};
通过重置托管窗口的DataContext将不会对TextBox刷新本身进行绑定
window.DataContext = newDictionaryData;
实际上,TextBox的DataContext仍然缓存旧的数据值。
TextBox似乎只在初始化时才获取其父DataContext的副本,然后才能使用该本地副本。
在这种情况下看起来似乎不容易拥有ViewModel并实现INotifyPropertyChanged,因为关键字“ID_XX”在不同的视图之间可能会有所不同;很难为这种动态特性定义Model类(我可以是错的。)
如果每次新数据到达时都创建了一个新的托管窗口(并设置了DataContext),它确实可以正常工作,因为所有TextBox的DataContext都将为新托管窗口派生新数据。
有没有人知道如何让TextBox“刷新”其DataContext以在父窗口上获取新的一组并“刷新”绑定?
答案 0 :(得分:2)
在WPF中,我们通常不会将DataContext
的{{1}}设置为这样的一个数据类型对象...但是 是可能的。相反,我们通常创建一个特定的类,其中包含要显示的所有属性,并且如您所提到的,实现Window
接口。在您的情况下,我们将在UI中绑定一个INotifyPropertyChanged
类型的属性:
Staff
然后在XAML中:
public string Staff
{
get { return staff; }
set { staff = value; NotifyPropertyChanged("Staff"); }
}
在这个小例子中,这不是绝对必要的,但在较大的项目中,可能还会显示其他数据。如果将<Window>
<StackPanel>
<TextBox Text="{Binding Staff.Id}"/>
<TextBox Text="{Binding Staff.Name}"/>
</StackPanel>
</Window>
设置为Window.DataContext
数据类型的一个实例,那么您会发现显示其他数据(例如Staff
个对象的集合)很棘手。同样,最好更新Staff
属性,这将通知界面更新UI,而不是更新UI的Staff
,因为它没有“连接”到界面。
通过DataContext
接口更新属性更改通知,或者在调用属性更改时“刷新”UI控件中的值。因此,您的答案是实现此界面,并确保在更改属性值时调用INotifyPropertyChanged
。
更新&gt;&gt;&gt;
哇!你真的不是在听,是吗?在WPF中,我们不会“刷新”INotifyPropertyChanged.PropertyChangedEventHandler
,DataContext
界面会在属性更改后刷新UI。这是解决此问题的一种可能方法:
INotifyPropertyChanged
然后你可以这样填写你的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
现在你可以认为你出于这个原因不能这样做,或者你不想因为这个原因那样做,但是在一天结束时,你必须做这样的事情可以解决你的问题。
答案 1 :(得分:1)
您可以考虑创建一个自定义的ObservableDictionary&lt; 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<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);
}
}
}
那是基础课。该课程中的一个示例方法:
/// <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;
}
不要忘记调查员:
/// <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();
}
现在显然我遗漏了一些实现,因为这最初是从我的尝试(大多数成功)中派生出一个只读的可观察字典。但基础确实有效。希望这有一些用处。
答案 2 :(得分:-1)
我已经通过将TextBox绑定到父托管Window的DataContext来解决了这个问题。
感谢大家的评论。