[英]Binding works only once
In the constructor, when I add two items to an ObservableCollection bound to an attached property of a TextBlock, the attached property is updated.在构造函数中,当我将两个项目添加到绑定到 TextBlock 的附加属性的 ObservableCollection 时,附加属性会更新。 But when I add items to the same ObservableCollection later in another method, the attached property is not updated.
但是当我稍后在另一个方法中将项目添加到同一个 ObservableCollection 时,附加属性不会更新。
XAML : XAML :
<TextBlock local:TextBlockExtensions.BindableInlines="{Binding StatusInlines}" />
TextBlockExtensions.cs : TextBlockExtensions.cs :
public class TextBlockExtensions : BaseViewModel
{
public static IEnumerable<Inline> GetBindableInlines(DependencyObject obj)
{
return (IEnumerable<Inline>)obj.GetValue(BindableInlinesProperty);
}
public static void SetBindableInlines(DependencyObject obj, IEnumerable<Inline> value)
{
obj.SetValue(BindableInlinesProperty, value);
}
public static readonly DependencyProperty BindableInlinesProperty =
DependencyProperty.RegisterAttached("BindableInlines", typeof(IEnumerable<Inline>), typeof(TextBlockExtensions), new PropertyMetadata(null, OnBindableInlinesChanged));
private static void OnBindableInlinesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var Target = d as TextBlock;
if (Target != null)
{
Target.Inlines.Clear();
Target.Inlines.AddRange((System.Collections.IEnumerable)e.NewValue);
}
}
}
ViewModel :视图模型:
public ObservableCollection<Inline> StatusInlines { get; set; } = new ObservableCollection<Inline>();
...
// works in the constructor (called twice)
StatusInlines.Add(new Run($"{ text }{ Environment.NewLine }") { ToolTip = "tooltip" });
...
// does not work later in other methods
StatusInlines.Add(new Run($"{ text }{ Environment.NewLine }") { ToolTip = "tooltip" });
BaseViewModel.cs (by angelsix): BaseViewModel.cs (由 angelsix 提供):
public class BaseViewModel : INotifyPropertyChanged
{
#region Protected Members
/// <summary>
/// A global lock for property checks so prevent locking on different instances of expressions.
/// Considering how fast this check will always be it isn't an issue to globally lock all callers.
/// </summary>
protected object mPropertyValueCheckLock = new object();
#endregion
/// <summary>
/// The event that is fired when any child property changes its value
/// </summary>
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
/// <summary>
/// Call this to fire a <see cref="PropertyChanged"/> event
/// </summary>
/// <param name="name"></param>
public void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#region Command Helpers
/// <summary>
/// Runs a command if the updating flag is not set.
/// If the flag is true (indicating the function is already running) then the action is not run.
/// If the flag is false (indicating no running function) then the action is run.
/// Once the action is finished if it was run, then the flag is reset to false
/// </summary>
/// <param name="updatingFlag">The boolean property flag defining if the command is already running</param>
/// <param name="action">The action to run if the command is not already running</param>
/// <returns></returns>
protected async Task RunCommandAsync(Expression<Func<bool>> updatingFlag, Func<Task> action)
{
// Lock to ensure single access to check
lock (mPropertyValueCheckLock)
{
// Check if the flag property is true (meaning the function is already running)
if (updatingFlag.GetPropertyValue())
return;
// Set the property flag to true to indicate we are running
updatingFlag.SetPropertyValue(true);
}
try
{
// Run the passed in action
await action();
}
finally
{
// Set the property flag back to false now it's finished
updatingFlag.SetPropertyValue(false);
}
}
/// <summary>
/// Runs a command if the updating flag is not set.
/// If the flag is true (indicating the function is already running) then the action is not run.
/// If the flag is false (indicating no running function) then the action is run.
/// Once the action is finished if it was run, then the flag is reset to false
/// </summary>
/// <param name="updatingFlag">The boolean property flag defining if the command is already running</param>
/// <param name="action">The action to run if the command is not already running</param>
/// <typeparam name="T">The type the action returns</param>
/// <returns></returns>
protected async Task<T> RunCommandAsync<T>(Expression<Func<bool>> updatingFlag, Func<Task<T>> action, T defaultValue = default(T))
{
// Lock to ensure single access to check
lock (mPropertyValueCheckLock)
{
// Check if the flag property is true (meaning the function is already running)
if (updatingFlag.GetPropertyValue())
return defaultValue;
// Set the property flag to true to indicate we are running
updatingFlag.SetPropertyValue(true);
}
try
{
// Run the passed in action
return await action();
}
finally
{
// Set the property flag back to false now it's finished
updatingFlag.SetPropertyValue(false);
}
}
#endregion
}
Right now you only set the TextBlock
inlines once.现在您只需设置一次
TextBlock
内联。
You forgot to subscribe to the INotifyCollectionChanged.CollectionChanged
event and to handle the changed items.您忘记订阅
INotifyCollectionChanged.CollectionChanged
事件并处理更改的项目。
The following refactored version of TextBlockExtensions
listens to INotifyCollectionChanged.CollectionChanged
in case the binding source implements INotifyCollectionChanged
and handles the changes accordingly:以下重构版本的
TextBlockExtensions
侦听INotifyCollectionChanged.CollectionChanged
,以防绑定源实现INotifyCollectionChanged
并相应地处理更改:
TextBlockExtensions.cs TextBlockExtensions.cs
public class TextBlockExtensions : BaseViewModel
{
public static readonly DependencyProperty BindableInlinesProperty =
DependencyProperty.RegisterAttached(
"BindableInlines",
typeof(IEnumerable<Inline>),
typeof(TextBlockExtensions),
new PropertyMetadata(default(IEnumerable<Inline>), OnBindableInlinesChanged));
public static IEnumerable<Inline> GetBindableInlines(DependencyObject obj) =>
(IEnumerable<Inline>) obj.GetValue(BindableInlinesProperty);
public static void SetBindableInlines(DependencyObject obj, IEnumerable<Inline> value) =>
obj.SetValue(BindableInlinesProperty, value);
private static Dictionary<INotifyCollectionChanged, IList<WeakReference<TextBlock>>> CollectionToTextBlockMap { get; set; }
static TextBlockExtensions()
{
TextBlockExtensions.CollectionToTextBlockMap =
new Dictionary<INotifyCollectionChanged, IList<WeakReference<TextBlock>>>();
}
private static void OnBindableInlinesChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is TextBlock textBlock))
{
throw new ArgumentException("Attaching element must be of type 'TextBlock'.");
}
TextBlockExtensions.Cleanup(textBlock, e.OldValue);
if (!(e.NewValue is IEnumerable<Inline> inlineElements))
{
return;
}
textBlock.Inlines.AddRange(inlineElements);
if (inlineElements is INotifyCollectionChanged observableCollection)
{
ObserveCollectionChanges(observableCollection, textBlock);
}
}
private static void Cleanup(TextBlock textBlock, object oldCollection)
{
textBlock.Inlines.Clear();
if (oldCollection is INotifyCollectionChanged oldObservableCollection)
{
oldObservableCollection.CollectionChanged -= TextBlockExtensions.UpdateTextBlockOnCollectionChanged;
TextBlockExtensions.CollectionToTextBlockMap.Remove(oldObservableCollection);
}
}
private static void ObserveCollectionChanges(INotifyCollectionChanged observableCollection, TextBlock textBlock)
{
if (TextBlockExtensions.CollectionToTextBlockMap.TryGetValue(
observableCollection,
out IList<WeakReference<TextBlock>> boundTextBoxes))
{
boundTextBoxes.Add(new WeakReference<TextBlock>(textBlock));
}
else
{
observableCollection.CollectionChanged += TextBlockExtensions.UpdateTextBlockOnCollectionChanged;
TextBlockExtensions.CollectionToTextBlockMap.Add(
observableCollection,
new List<WeakReference<TextBlock>>() {new WeakReference<TextBlock>(textBlock)});
}
}
private static void UpdateTextBlockOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (TextBlockExtensions.CollectionToTextBlockMap.TryGetValue(
sender as INotifyCollectionChanged,
out IList<WeakReference<TextBlock>> boundTextBlocks))
{
var textBlockReferences = boundTextBlocks.ToList();
foreach (WeakReference<TextBlock> boundTextBlockReference in textBlockReferences)
{
if (boundTextBlockReference.TryGetTarget(out TextBlock textBlock))
{
UpdateCollection(textBlock, e);
}
else
{
// TextBlock already collected by the GC. Cleanup.
boundTextBlocks.Remove(boundTextBlockReference);
}
}
}
}
private static void UpdateCollection(TextBlock textBlock, NotifyCollectionChangedEventArgs eventArgs)
{
switch (eventArgs.Action)
{
case NotifyCollectionChangedAction.Add:
AddNewInlines(eventArgs.NewItems.OfType<Inline>(), textBlock);
break;
case NotifyCollectionChangedAction.Remove:
RemoveInlines(eventArgs.OldItems.OfType<Inline>(), textBlock);
break;
case NotifyCollectionChangedAction.Replace:
ReplaceInlines(eventArgs, textBlock);
break;
case NotifyCollectionChangedAction.Move:
MoveInlines(eventArgs, textBlock);
break;
case NotifyCollectionChangedAction.Reset:
textBlock.Inlines.Clear();
break;
}
}
private static void AddNewInlines(IEnumerable<Inline> newItems, TextBlock textBlock)
{
foreach (Inline newItem in newItems)
{
textBlock.Inlines.Add(newItem);
}
}
private static void RemoveInlines(IEnumerable<Inline> removedItems, TextBlock textBlock)
{
foreach (Inline removedItem in removedItems)
{
textBlock.Inlines.Remove(removedItem);
}
}
private static void ReplaceInlines(NotifyCollectionChangedEventArgs eventArgs, TextBlock textBlock)
{
int currentReplaceIndex = eventArgs.NewStartingIndex;
List<Inline> replacedItems = eventArgs.OldItems.OfType<Inline>().ToList();
List<Inline> replacementItems = eventArgs.NewItems.OfType<Inline>().ToList();
for (int changedItemsIndex = 0; changedItemsIndex < replacementItems.Count; changedItemsIndex++)
{
Inline replacedItem = textBlock.Inlines.ElementAt(currentReplaceIndex++);
Inline replacementItem = replacementItems.ElementAt(changedItemsIndex);
textBlock.Inlines.InsertAfter(replacedItem, replacementItem);
textBlock.Inlines.Remove(replacedItem);
}
}
private static void MoveInlines(NotifyCollectionChangedEventArgs eventArgs, TextBlock textBlock)
{
foreach (Inline movedItem in eventArgs.OldItems.OfType<Inline>())
{
Inline currentItemAtNewPosition = textBlock.Inlines.ElementAt(eventArgs.NewStartingIndex);
textBlock.Inlines.Remove(movedItem);
textBlock.Inlines.InsertAfter(currentItemAtNewPosition, movedItem);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.