刷新WPF中子控件的DataContext

时间:2013-10-09 09:23:29

标签: c# wpf xaml inheritance datacontext

我们有一个客户端 - 服务器应用程序,它需要动态构建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以在父窗口上获取新的一组并“刷新”绑定?

3 个答案:

答案 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.PropertyChangedEventHandlerDataContext界面会在属性更改后刷新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&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();
    }

现在显然我遗漏了一些实现,因为这最初是从我的尝试(大多数成功)中派生出一个只读的可观察字典。但基础确实有效。希望这有一些用处。

答案 2 :(得分:-1)

我已经通过将TextBox绑定到父托管Window的DataContext来解决了这个问题。

感谢大家的评论。