ObservableCollection as DependencyProperty - how to add / remove items in CollectionChanged event? - wpf

I have the following code:
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.RegisterAttached("ItemsSource", typeof (ObservableCollection<BaseViewModel>),
typeof (MultiSelectComboBoxUserControl),
new FrameworkPropertyMetadata(null, OnItemsSourceChanged));
public static ObservableCollection<BaseViewModel> GetItemsSource(DependencyObject obj)
{
return (ObservableCollection<BaseViewModel>) obj.GetValue(ItemsSourceProperty);
}
public static void SetItemsSource(DependencyObject obj, ObservableCollection<BaseViewModel> value)
{
obj.SetValue(ItemsSourceProperty, value);
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged) e.OldValue;
coll.CollectionChanged -= ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<BaseViewModel>) e.NewValue;
coll.CollectionChanged += ItemsSource_CollectionChanged;
}
}
private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Here I'd like to update my ObservableCollection - ItemsSource
}
How can I achieve this (updating ItemsSource)? I can't access it because it's a dependency property and event handler is a static method. Any tips very welcome.

The object that you receive as the sender parameter is the ObservableCollection. If you plan on changing the collection (such as adding or removing items) you may also need to detach the event handler before making changes.
private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ObservableCollection collection = sender as ObservableCollection<BaseViewModel>;
if (collection != null)
{
collection.CollectionChanged -= ItemsSource_CollectionChanged;
//Update ObservableCollection
collection.CollectionChanged += ItemsSource_CollectionChanged;
}
}

I wouldn't change my ItemsSource instance at all. I would simply create my ObservableCollection instance and clear, add items when updating.

Ok, it simple!
Old code:
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgse)
{
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged) e.OldValue;
coll.CollectionChanged -= ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<BaseViewModel>) e.NewValue;
coll.CollectionChanged += ItemsSource_CollectionChanged;
}
}
New Code:
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgse)
{
(YouCustomControl) control = (YouCustomControl)d;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged) e.OldValue;
coll.CollectionChanged -= control.ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<BaseViewModel>) e.NewValue;
coll.CollectionChanged += control.ItemsSource_CollectionChanged;
}
}
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}

Related

How to handle items added to Attached Property of ObservableCollection type

I have a UWP project that uses MapControl, which is a sealed class - cant derive a new class from it.
Trying to make a bindable Attached Property, which would have access to MapControl.Children.
The problem is that it only works when I set ViewModel's collection, but not when I add a new element to that collection:
// Works fine
this.MapChildrenExtCollection = new ObservableCollection<MapChildElement>();
// Nothing happens
this.MapChildrenExtCollection.Add(new MapChildElement());
Heres my code for the Attached Property:
namespace UWPMap.Extensions
{
public class MapControlExt : DependencyObject
{
public static readonly DependencyProperty ChildrenExtProperty = DependencyProperty.Register(
"ChildrenExt",
typeof(ObservableCollection<MapChildElement>),
typeof(MapControlExt),
new PropertyMetadata(new ObservableCollection<MapChildElement>(), ChildrenExtPropertyChanged));
public ObservableCollection<MapChildElement> ChildrenExt
{
get { return (ObservableCollection<MapChildElement>)GetValue(ChildrenExtProperty); }
set { SetValue(ChildrenExtProperty, value); }
}
public static void SetChildrenExt(UIElement element, ObservableCollection<MapChildElement> value)
{
element.SetValue(ChildrenExtProperty, value);
}
public static ObservableCollection<MapChildElement> GetChildrenExt(UIElement element)
{
return (ObservableCollection<MapChildElement>)element.GetValue(ChildrenExtProperty);
}
private static void ChildrenExtPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = (MapControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
if (newCollection != null)
{
oldCollection.CollectionChanged += Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
ManageChildrenExt();
}
static void ChildrenExtCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
ManageChildrenExt();
}
static private void ManageChildrenExt()
{
// Access MapControl.Children here
}
}
}
XAML:
<maps:MapControl x:Name="MyMap"
ext:MapControlExt.ChildrenExt="{x:Bind Path=MapChildrenExtCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</maps:MapControl>
The problem is that you are not adding the event handler to the new collection and using oldCollection variable by mistake.
The following snippet:
if (newCollection != null)
{
oldCollection.CollectionChanged += //here you have oldCollection by mistake
Extensions.MapControlExt.ChildrenExtCollectionChanged;
}
Should be:
if (newCollection != null)
{
newCollection.CollectionChanged +=
Extensions.MapControlExt.ChildrenExtCollectionChanged;
}

WPF - Access DependencyObject inside a CollectionChanged event

I have an user control that combines the values of 2 DepedenciyProperties:
int numberPeople and ingredients Lists<>
I want when either of these values updates to redo the combination.
My current implementation uses a static variable to keep track of the object instance (objectInstance). I am wondering if there is a cleaner way to do this.
private static DependencyObject objectInstance;
public int numberPeople
{
get { return (int)GetValue(numberPeopleProperty); }
set { SetValue(numberPeopleProperty, value); }
}
public static readonly DependencyProperty numberPeopleProperty =
DependencyProperty.Register("numberPeople", typeof(int), typeof(ListDisplayer), new PropertyMetadata(0, Combine));
public ObservableCollection<ListModel> ingredients
{
get { return (ObservableCollection<ListModel>)GetValue(ingredientsProperty); }
set { SetValue(ingredientsProperty, value); }
}
public static readonly DependencyProperty ingredientsProperty =
DependencyProperty.Register("ingredients", typeof(ObservableCollection<ListModel>), typeof(ListDisplayer), new PropertyMetadata(null, AssignCollectionChangedToList));
private static void AssignCollectionChangedToList(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as ListDisplayer;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
instance.ItemsSource = (ObservableCollection<ListModel>)e.NewValue;
objectInstance = instance;
instance.ItemsSource.CollectionChanged += ItemsSource_CollectionChanged;
}
}
private static void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// MY PROBLEM: when a new item is added in this list trigger again Combine(), is there
// another way to trigger the Combine so that it will process the IngredientList and numberPeople attached to the object ?
Combine(objectInstance, new DependencyPropertyChangedEventArgs());
}
private static void Combine(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// process numberPeople and ingredientList
}
Edit: Remove the static keyword from the definition of the ItemsSource_CollectionChanged event handler and hook it up using the "instance" reference:
private static void AssignCollectionChangedToList(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as ListDisplayer;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= instance.ItemsSource_CollectionChanged;
}
if (e.NewValue != null)
{
instance.ingredients = (ObservableCollection<ListModel>)e.NewValue;
objectInstance = instance;
instance.ingredients.CollectionChanged += instance.ItemsSource_CollectionChanged;
}
}
private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Combine(this, new DependencyPropertyChangedEventArgs());
}
Also the CLR wrappers of your dependency properties are not correctly implemented. You should pass the dependency property to the GetValue and SetValue methods:
public int numberPeople
{
get { return (int)GetValue(numberPeopleProperty); }
set { SetValue(numberPeopleProperty, value); }
}
public static readonly DependencyProperty numberPeopleProperty =
DependencyProperty.Register("numberPeople", typeof(int), typeof(ListDisplayer), new PropertyMetadata(0, Combine));
public ObservableCollection<ListModel> ingredients
{
get { return (ObservableCollection<ListModel>)GetValue(ingredientsProperty); }
set { SetValue(ingredientsProperty, value); }
}
public static readonly DependencyProperty ingredientsProperty =
DependencyProperty.Register("ingredients", typeof(ObservableCollection<ListModel>), typeof(ListDisplayer), new PropertyMetadata(null, AssignCollectionChangedToList));

Selecting text and caret in a texbox on focus

public class TextBoxSelectionBehavior : Behavior<TextBox>
{
public static bool GetSelectAllTextOnFocus(TextBox textBox)
{
return (bool)textBox.GetValue(SelectAllTextOnFocusProperty);
}
public static void SetSelectAllTextOnFocus(TextBox textBox, bool value)
{
textBox.SetValue(SelectAllTextOnFocusProperty, value);
}
public static readonly DependencyProperty SelectAllTextOnFocusProperty =
DependencyProperty.RegisterAttached(
"SelectAllTextOnFocus",
typeof(bool),
typeof(TextBoxSelectionBehavior),
new UIPropertyMetadata(false, OnSelectAllTextOnFocusChanged));
private static void OnSelectAllTextOnFocusChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox == null) return;
if (e.NewValue is bool == false) return;
if ((bool)e.NewValue)
{
textBox.GotFocus += SelectAll;
textBox.PreviewMouseDown += IgnoreMouseButton;
}
else
{
textBox.GotFocus -= SelectAll;
textBox.PreviewMouseDown -= IgnoreMouseButton;
}
}
private static void SelectAll(object sender, RoutedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox == null) return;
textBox.SelectAll();
textBox.CaretIndex = textBox.Text.Length;
}
private static void IgnoreMouseButton(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null || textBox.IsKeyboardFocusWithin) return;
textBox.Focus();
e.Handled = true;
}
The textbox in question is an editable cell.
Once the cell is double clicked, i want a selectall of the text as well as a caret blink at the end of the text, on getfocus.
The text is highlighted correctly.
But setting the caret at the end is deselecting the highlighted text.
i cant do both at once. how to accomplish this?
I have tried selection start,length and other approaches.nothing works.

Modify WPF dependency property when pressing key

I have a WPF UserControl with a certain dependency property DepProp.
I would like this property to be modified when I press Shift or Alt, and to return to the previous value when releasing the keys.
What I want is similar to a trigger, but I don't know if it's possible to set the condition to be something like "Shift key is pressed".
I know that it's possible to specify KeyBindings for the control, as far as I understood they can execute a command when a key is pressed, but don't restore the previous vlaue when the key is released.
Any idea on how to do this?
You could create an attached behavior that you can affix to some "scope" element (e.g., your UserControl) that will maintain an attached read-only property that gets inherited down the tree. Then you can simply add a Trigger on the attached property.
public sealed class AltShiftHotKeyBehavior : Behavior<FrameworkElement>
{
private const ModifierKeys AltShift = ModifierKeys.Alt | ModifierKeys.Shift;
private static readonly DependencyPropertyKey IsAltShiftPressedPropertyKey =
DependencyProperty.RegisterAttachedReadOnly(
"IsAltShiftPressed",
typeof(bool),
typeof(AltShiftHotKeyBehavior),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.Inherits));
public static readonly DependencyProperty IsAltShiftPressedProperty =
IsAltShiftPressedPropertyKey.DependencyProperty;
public static bool GetIsAltShiftPressed(DependencyObject element)
{
return (bool)element.GetValue(IsAltShiftPressedProperty);
}
protected override void OnAttached()
{
base.OnAttached();
var element = this.AssociatedObject;
element.AddHandler(
FrameworkElement.LoadedEvent,
(RoutedEventHandler)OnLoaded,
handledEventsToo: true);
element.AddHandler(
FrameworkElement.UnloadedEvent,
(RoutedEventHandler)OnUnloaded,
handledEventsToo: true);
element.AddHandler(
UIElement.PreviewKeyDownEvent,
(KeyEventHandler)OnKey,
handledEventsToo: true);
element.AddHandler(
UIElement.PreviewKeyUpEvent,
(KeyEventHandler)OnKey,
handledEventsToo: true);
element.AddHandler(
UIElement.LostKeyboardFocusEvent,
(KeyboardFocusChangedEventHandler)OnLostKeyboardFocus,
handledEventsToo: true);
var window = element as Window;
if (window != null)
{
window.Activated += OnWindowActivated;
window.Deactivated += OnWindowDeactivated;
}
CheckToggledState();
}
protected override void OnDetaching()
{
ClearToggledState();
base.OnDetaching();
var element = this.AssociatedObject;
element.RemoveHandler(
FrameworkElement.LoadedEvent,
(RoutedEventHandler)OnLoaded);
element.RemoveHandler(
FrameworkElement.UnloadedEvent,
(RoutedEventHandler)OnUnloaded);
element.RemoveHandler(
UIElement.PreviewKeyDownEvent,
(KeyEventHandler)OnKey);
element.RemoveHandler(
UIElement.PreviewKeyUpEvent,
(KeyEventHandler)OnKey);
element.RemoveHandler(
UIElement.LostKeyboardFocusEvent,
(KeyboardFocusChangedEventHandler)OnLostKeyboardFocus);
var window = element as Window;
if (window != null)
{
window.Activated -= OnWindowActivated;
window.Deactivated -= OnWindowDeactivated;
}
}
private void CheckToggledState()
{
var element = this.AssociatedObject;
if (element.IsLoaded &&
element.IsKeyboardFocusWithin &&
Keyboard.PrimaryDevice.Modifiers == AltShift)
{
element.SetValue(IsAltShiftPressedPropertyKey, true);
}
else
{
element.ClearValue(IsAltShiftPressedPropertyKey);
}
}
private void ClearToggledState()
{
this.AssociatedObject.ClearValue(IsAltShiftPressedPropertyKey);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
CheckToggledState();
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
ClearToggledState();
}
private void OnWindowActivated(object sender, EventArgs e)
{
CheckToggledState();
}
private void OnWindowDeactivated(object sender, EventArgs e)
{
ClearToggledState();
}
private void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
CheckToggledState();
}
private void OnKey(object sender, KeyEventArgs e)
{
CheckToggledState();
}
}

wpf trigger dependency property refresh manually

I have a custom UserControl subclassing from RichTextBox. This class has a dependency property, Equation, that is bound two-way.
When the user drops an item onto the control I change Equation. This properly propagates the change to the other end of the binding, which triggers a property changed notification, but the UI is not changing. If I change the binding to a different object and back it then displays the updated Equation.
How can I force the refresh without changing the binding? Right now I'm setting Equation=null and then back which works, but that seems hackish. There must be something more elegant.
Here are relevant portions of the control. What I would like to happen is for the OnEquationChanged callback to be called after I change Equation (Equation.Components.Add(txt)).
public class EquationTextBox : RichTextBox
{
protected override void OnDrop(DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.StringFormat))
{
string str = (string)e.Data.GetData(DataFormats.StringFormat);
EquationText txt = new EquationText(str);
//// Preferred /////
Equation.Components.Add(txt);
//// HACK /////
Equation eqn = this.Equation;
eqn.Components.Add(txt);
this.Equation = null;
this.Equation = eqn;
///////////////
Console.WriteLine("Dropping " + str);
}
}
public Equation Equation
{
get { return (Equation)GetValue(EquationProperty); }
set { SetValue(EquationProperty, value); }
}
private static void onEquationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string prop = e.Property.ToString();
EquationTextBox txtBox = d as EquationTextBox;
if(txtBox == null || txtBox.Equation == null)
return;
FlowDocument doc = txtBox.Document;
doc.Blocks.Clear();
doc.Blocks.Add(new Paragraph(new Run(txtBox.Equation.ToString())));
}
public static readonly DependencyProperty EquationProperty =
DependencyProperty.Register("Equation",
typeof(Equation),
typeof(EquationTextBox),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(onEquationChanged)));
private bool mIsTextChanged;
}
}
Here is the property on the other end of the two-way binding. The equation_PropertyChanged event is getting called in the above code as a result of Equation.Components.Add(txt);
public Equation Equation
{
get{ return mEquation; }
set { mEquation = value; NotifyPropertyChanged(); }
}
private void equation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("Equation");
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
Edit --------------------------
Per the comments, I tried using a dispatcher like this (note that this is my first attempt at using a dispatcher)
string str = (string)e.Data.GetData(DataFormats.StringFormat);
EquationText txt = new EquationText(str);
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
Equation.Components.Add(txt);
NotifyPropertyChanged("Equation");
}));
but still no UI update.
Edit 2 --------------------------
The 2-way binding is done in XAML
<l:EquationTextBox x:Name="ui_txtVariableEquation" Grid.Row="0" Grid.Column="2"
Grid.RowSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AllowDrop="True"
Equation="{Binding SelectedVariableVM.Variable.Equation, Mode=TwoWay}">
</l:EquationTextBox>
Info relevant to the Components object (with in the Equation class)
public class Equation : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Equation()
{
mComponents = new ObservableCollection<EquationComponent>();
mComponents.CollectionChanged += new NotifyCollectionChangedEventHandler(components_CollectionChanged);
}
public Equation(string eqn) : this()
{
mComponents.Add(new EquationText(eqn));
}
public ObservableCollection<EquationComponent> Components
{
get{ return mComponents; }
set{ mComponents = value; NotifyPropertyChanged();}
}
public override string ToString()
{
string str = "";
for(int i=0; i<mComponents.Count; i++)
str += mComponents[i].ToString();
return str;
}
private void components_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Components");
}
private ObservableCollection<EquationComponent> mComponents;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Variable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Variable(string name = "var", VariableType type = VariableType.UnknownType) :
this(name, "", 0, type)
{
}
and ...
public class Variable : INotifyPropertyChanged
{
public Variable(string name, string unit, object val, VariableType type)
{
mEquation = new Equation(name + " = " + val.ToString() +
mEquation.PropertyChanged += new PropertyChangedEventHandler(equation_PropertyChanged);
}
...
public Equation Equation
{
get{ return mEquation; }
set { mEquation = value; NotifyPropertyChanged(); }
}
private void equation_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged("Equation");
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private Equation mEquation;
...
}
Variable.equation_PropertyChanged is called when the event is raised inside of the Equation class
I think the problem is that the value produced by the binding is not actually changing (it's still the same Equation object). If the DP value doesn't change, then your DP change handler will not be called.
Perhaps, in your DP change handler, you should subscribe to the new equation's PropertyChanged event and then rebuild your document when an underlying property changes:
private static void onEquationChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var txtBox = d as EquationTextBox;
if (txtBox == null)
return;
var oldEquation = e.OldValue as Equation;
if (oldEquation != null)
oldEquation.PropertyChanged -= txtBox.OnEquationPropertyChanged;
var newEquation = e.NewValue as Equation;
if (newEquation != null)
newEquation.PropertyChanged += txtBox.OnEquationPropertyChanged;
txtBox.RebuildDocument();
}
private void OnEquationPropertyChanged(object sender, EventArgs e)
{
RebuildDocument();
}
private void RebuildDocument()
{
FlowDocument doc = this.Document;
doc.Blocks.Clear();
var equation = this.Equation;
if (equation != null)
doc.Blocks.Add(new Paragraph(new Run(equation.ToString())));
}

Resources