2

ビューを動的に構築する必要があるクライアント/サーバー アプリケーションがあります。サーバーは、データ (Dctionary< string, string>) と共に XAML 文字列をクライアントに送信します。クライアントは、受信した Xaml 文字列からビューを構築し、データをビューにバインドします。

サンプル 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;

2 つの TextBox は Window から DataContext を継承するため、Text プロパティは Dictionary にバインドされます。バインディング コンバーター「fieldBindingConverter」は、キーを持つ ConverterParameter を使用して、辞書から正しい値をフェッチします。

そのため、2 つの 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」はビューによって異なる可能性があり、この動的な性質のためにモデル クラスを定義するのは難しいためです (私は間違っている可能性があります)。 .

すべての TextBox の DataContext には新しいホスティング ウィンドウ用に派生した新しいデータがあるため、新しいデータが到着するたびに新しいホスティング ウィンドウが作成される (および DataContext が設定される) 場合、正しく機能します。

TextBox の DataContext を「更新」して、親ウィンドウに設定された新しいものを取得し、バインディングを「更新」する方法を知っている人はいますか?

4

3 に答える 3

2

DataContextWPF では、通常、このように aWindowを 1 つのデータ型オブジェクトに設定しません、可能です。代わりに、通常、表示に必要なすべてのプロパティを含む特定のクラスを作成し、言及したように、INotifyPropertyChangedインターフェイスを実装します。あなたの場合、StaffUI でバインドできるタイプのプロパティがあります。

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をデータ型の 1 つのインスタンスだけに設定するStaffと、オブジェクトのコレクションなど、他のデータを表示するのが難しくなりStaffます。同様に、インターフェイスに「接続」されていないために UI を更新しないStaffよりも、UI を更新するようにインターフェイスに通知するプロパティを更新する方が適切です。DataContext

INotifyPropertyChangedこれは、プロパティの変更が行われたときに UI コントロールの値を更新する、または呼び出すと「更新」するインターフェイスを介したプロパティの変更の通知です。したがって、あなたの答えは、このインターフェイスを実装しINotifyPropertyChanged.PropertyChangedEventHandler、プロパティ値の変更が行われたときに を呼び出すことを確認することです。

更新 >>>

わお!本当に聞いてないですよね?WPF では、プロパティが変更されるとインターフェイスが UI を「更新」しますDataContextINotifyPropertyChangedこれは、この問題を解決できる 1 つの方法です。

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>

この理由でこれを行うことができない、またはその理由でそれをしたくないと主張することができますが、結局のところ、問題を解決するにはこのようなことをしなければなりませ

于 2013-10-09T09:45:05.513 に答える
1

ObservalbeCollection クラスから継承されたカスタム ObservableDictionary < T , U > クラスを作成することを検討してください。私はこれをやり遂げました。ねじれを取り除くのにいくらかの作業が必要でしたが、これは私の最も貴重なカスタム クラスの 1 つになりました。提案としてのいくつかの短いコード:

/// <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();
    }

これはもともと、読み取り専用の監視可能な辞書を作成する試み (ほとんど成功) から派生したものであるため、明らかにいくつかの実装を省略しました。しかし、基本は機能します。これが役に立つことを願っています。

于 2013-10-09T11:01:56.423 に答える