Programatically change state of GridViewCheckBoxColumn row with updating binding source for Telerik's GridView - wpf

I'm making a custom behavior for Telerik's RadGridView.
When this behavior is attached and its PropertyName is set to same property as specified by
DataMemberBinding value of some of the GridViewCheckBoxColumn of the grid, then toggling the checkbox in that column will apply same checkbox state to all selected rows (but only to the same column).
That happens in the ApplyToAllSelected method, namely in gvcb.SetCurrentValue(GridViewCheckBox.IsCheckedProperty, isChecked); line. The visuals are working as expected, and all checkbox values are updated on screen.
The Problem is that the binding source is not updated for those rows. Only for the one where click happened. GridViewCheckBox.IsChecked dependency property does not seem to be bound directly to the datacontext's property, so gvcb.GetBindingExpression(GridViewCheckBox.IsChecked) returns null.
The Question: how to update source after setting checkbox state?
public sealed class CheckAllSelectedBehavior : Behavior<RadGridView>
{
public event EventHandler Toggled;
public string PropertyName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.PreparingCellForEdit += this.AssociatedObject_PreparedCellForEdit;
this.AssociatedObject.CellEditEnded += this.AssociatedObject_CellEditEnded;
}
protected override void OnDetaching()
{
this.AssociatedObject.PreparingCellForEdit -= this.AssociatedObject_PreparedCellForEdit;
this.AssociatedObject.CellEditEnded -= this.AssociatedObject_CellEditEnded;
base.OnDetaching();
}
private void AssociatedObject_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == this.PropertyName && e.EditingElement is CheckBox cb)
{
cb.Checked -= this.Cb_Checked;
cb.Unchecked -= this.Cb_Unchecked;
}
}
private void AssociatedObject_PreparedCellForEdit(object sender, GridViewPreparingCellForEditEventArgs e)
{
if (e.Column.UniqueName == this.PropertyName && e.EditingElement is CheckBox cb)
{
cb.Checked += this.Cb_Checked;
cb.Unchecked += this.Cb_Unchecked;
}
}
private void Cb_Unchecked(object sender, System.Windows.RoutedEventArgs e)
{
this.ApplyToAllSelected(false);
}
private void Cb_Checked(object sender, System.Windows.RoutedEventArgs e)
{
this.ApplyToAllSelected(true);
}
private void ApplyToAllSelected(bool isChecked)
{
foreach (var item in this.AssociatedObject.SelectedItems)
{
var row = this.AssociatedObject.GetRowForItem(item);
var cell = row.GetCellFromPropertyName(this.PropertyName);
if (cell.Content is GridViewCheckBox gvcb)
{
gvcb.SetCurrentValue(GridViewCheckBox.IsCheckedProperty, isChecked);
}
}
this.Toggled?.Invoke(this, EventArgs.Empty);
}
}

Using reflection to set the value on viewmodel property seems to work. Modify the ApplyToAllSelected method like follows:
private void ApplyToAllSelected(bool isChecked)
{
foreach (var item in this.AssociatedObject.SelectedItems)
{
this.SetProperty(item, isChecked);
}
this.Toggled?.Invoke(this, EventArgs.Empty);
}
private void SetProperty(object target, bool isChecked)
{
var prop = target.GetType().GetProperty(
this.PropertyName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
prop.SetValue(target, isChecked);
}

Related

Property Changed from ObservableCollectio<T> to Main class

I think this is a stupid problem but...
I have a Window class with a Treeview bind to a Observablecollection<T> foo; T is my class (in another file .cs).
My Observablecollection foo have OnPropertyChanged for add/delete element and works ok.
The problem is for my class T: it has 5 properties and one have OnPropertyChanged: when I programmatically change an element of my class T I have OnPropertyChanged in class T (obviously) but... how can I know it from Window class for do something?
You want to be able to listen to propertychange events on the items inside the observable collection?
To do this, I created an extension to ObsercableCollection that bubbles up these events to a collection level, so I can subscribe to collection.CollectionChanged (adding and removing items) and collection.ItemInCollectionChange (property of an item inside the collection has changed):
//public class ItemInCollectionChangeEventArgs
public class ItemInCollectionChangeEventArgs : EventArgs
{
public object Item { get; internal set; }
public PropertyChangedEventArgs PropertyChangedEventArgs { get; internal set; }
}
public delegate void ItemInCollectionChangeEventHandler(Object sender, ItemInCollectionChangeEventArgs e);
//TODO Make this throw a separate event for refreshing
public class ItemsChangeObservableCollection<T> :
ObservableCollection<T> where T : INotifyPropertyChanged
{
public event ItemInCollectionChangeEventHandler ItemInCollectionChangeEvent;
public ItemsChangeObservableCollection() : base()
{
}
public ItemsChangeObservableCollection(IEnumerable<T> collection) : base()
{
foreach (T item in collection)
{
Add(item);
}
}
protected virtual void OnItemInCollectionChange(ItemInCollectionChangeEventArgs e)
{
ItemInCollectionChangeEventHandler handler = ItemInCollectionChangeEvent;
if (handler != null)
{
handler(this, e);
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
RegisterPropertyChanged(e.NewItems);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
UnRegisterPropertyChanged(e.OldItems);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
UnRegisterPropertyChanged(e.OldItems);
RegisterPropertyChanged(e.NewItems);
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
UnRegisterPropertyChanged(this);
base.ClearItems();
}
private void RegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void UnRegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemInCollectionChange(new ItemInCollectionChangeEventArgs { Item = sender, PropertyChangedEventArgs = e });
//base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}

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

TextBox AttachedProperty to Select All text not working as expected?

I have an attached property called "SelectAllOnFocus". Values of true/false.
public static class TextBoxProps
{
private static void MyTextBoxKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
((TextBox)sender).Text = string.Empty;
}
}
public static void SetSelectAllOnFocus(DependencyObject dependencyObject, bool selectAllOnFocus)
{
if (!ReferenceEquals(null, dependencyObject))
{
dependencyObject.SetValue(SelectAllOnFocus, selectAllOnFocus);
}
}
public static bool GetSelectAllOnFocus(DependencyObject dependencyObject)
{
if (!ReferenceEquals(null, dependencyObject))
{
return (bool)dependencyObject.GetValue(SelectAllOnFocus);
}
else
{
return false;
}
}
private static void OnSelectAllOnFocus(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool selectAllOnFocus = (bool)e.NewValue == true;
var theTextBox = d as TextBox;
if (selectAllOnFocus && theTextBox != null)
{
theTextBox.PreviewMouseDown -= MyTextBoxMouseEnter; theTextBox.PreviewMouseDown += MyTextBoxMouseEnter;
}
}
private static void MyTextBoxMouseEnter(object sender, MouseEventArgs e)
{
((TextBox)sender).SelectAll();
e.Handled = false;
}
public static readonly DependencyProperty SelectAllOnFocus
= DependencyProperty.RegisterAttached("SelectAllOnFocus", typeof(bool), typeof(TextBoxEscapeProperty),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnSelectAllOnFocus)));
}
What happens is the following:
The PreviewMouseDown event gets triggered.
The MyTextBoxMouseEnter method gets called.
The SelectAll() Method gets called.
When I do a "watch" on ((TextBox)sender).SelectedText, the value is correct (meaning whatever is in the textbox is showing up as selectedText).
The textbox itself is unchanged. No text is selected.
This is part of a general WPF style. All textboxes in the application should receive this property and it's associated behavior.
I'm stumped. Any ideas?
Thanks
What happens if you call ((TextBox)sender).UpdateLayout(); immediately after the SelectAll command? Or maybe you need to set the Keyboard focus to the text box.
It might be a better option to use something like this, which works if the text box is being selected with the mouse or the keyboard. (You'll need to modify it to check your "SelectAllOnFocus" property)
In your App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
// Select the text in a TextBox when it receives focus.
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(SelectivelyIgnoreMouseButton));
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText));
EventManager.RegisterClassHandler(typeof(TextBox), TextBox.MouseDoubleClickEvent, new RoutedEventHandler(SelectAllText));
base.OnStartup(e);
}
void SelectivelyIgnoreMouseButton(object sender, MouseButtonEventArgs e)
{
// Find the TextBox
DependencyObject parent = e.OriginalSource as UIElement;
while (parent != null && !(parent is TextBox))
parent = VisualTreeHelper.GetParent(parent);
if (parent != null)
{
var textBox = (TextBox)parent;
if (!textBox.IsKeyboardFocusWithin)
{
// If the text box is not yet focused, give it the focus and
// stop further processing of this click event.
textBox.Focus();
e.Handled = true;
}
}
}
void SelectAllText(object sender, RoutedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox != null)
textBox.SelectAll();
}

Extending a control (ComboBox) in silverlight

I have a simple problem. I am trying to add a dependency property to a combo box. I want to be able to display a value on the face of the combo box when it is initially displayed. There is a ContentPresenter with a TextBlock inside the ComboBox. That TextBlock gets set when a user selects an item in the ComboBox. How can I set that with a default value (not one of the items)? Show something like 'choose one'.
I can extend the ComboBox with a 'DefaultDisplay' dependency property but how do I 'link' that property to the TextBlock that is part of the control template?
Thanks for any help on this.
Pat
Instead of a dependency property I'd suggest using a behavior to do this. I actually already had one of these written for this problem. Give it a try by adding it to your combobox and setting the PromptText property on the behavior:
public class ComboBoxPromptBehavior : Behavior<ComboBox>
{
[Category("Display")]
public string PromptText
{
get { return (string)GetValue(PromptTextProperty); }
set { SetValue(PromptTextProperty, value); }
}
public static readonly DependencyProperty PromptTextProperty = DependencyProperty.Register("PromptText", typeof(string), typeof(ComboBoxPromptBehavior), new PropertyMetadata(" "));
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
AssociatedObject.LayoutUpdated += new EventHandler(AssociatedObject_LayoutUpdated);
}
void AssociatedObject_LayoutUpdated(object sender, EventArgs e)
{
SetPromptText();
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= new RoutedEventHandler(AssociatedObject_Loaded);
AssociatedObject.LayoutUpdated -= new EventHandler(AssociatedObject_LayoutUpdated);
}
void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
SetPromptText();
}
private void SetPromptText()
{
var textbox = AssociatedObject.FindChild<TextBlock>();
if (textbox != null && string.IsNullOrWhiteSpace(textbox.Text))
{
textbox.Text = PromptText;
}
}
}
And the extension method for FindChild is:
public static T FindChild<T>(this DependencyObject element) where T : DependencyObject
{
var childCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(element, i);
if (child is T)
{
return (T)child;
}
var match = child.FindChild<T>();
if (match != null) return match;
}
return null;
}

ObservableCollection has Depedency Property in Silverlight

Recently i was developing a custom control in Silverlight, I created custom dependency property which is of type ObservableCollection. I have another 2 custom dependency properties of type strings. My requirement is on addition of any item to collection, I have to fire collectionChanged Event , in this event handler, i want to update the other 2 dependency properties.
public static readonly DependencyProperty itemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<ValidationErrorMessage>), typeof(SummaryUserControl), new PropertyMetadata(new ObservableCollection<ValidationErrorMessage>(), new PropertyChangedCallback(fun1)));
public ObservableCollection<ValidationErrorMessage> Items
{
get
{
return (ObservableCollection<ValidationErrorMessage>)base.GetValue(itemsProperty);
}
set
{
base.SetValue(itemsProperty, value);
}
}
public static void fun1(object sender, DependencyPropertyChangedEventArgs evt)
{
var newValue = evt.NewValue as ObservableCollection<ValidationErrorMessage>;
if(newValue!=null)
newValue.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChangedHandler);
var oldValue = evt.OldValue as ObservableCollection<ValidationErrorMessage>;
if(oldValue!=null)
oldValue.CollectionChanged -= new NotifyCollectionChangedEventHandler(CollectionChangedHandler);
}
static void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var newItems = e.NewItems as ObservableCollection<ValidationErrorMessage>;
foreach (var item in newItems)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
public static readonly DependencyProperty headerProperty = DependencyProperty.Register("Header", typeof(String), typeof(SummaryUserControl), new PropertyMetadata(String.Empty, null));
public String Header
{
get
{
return (String)base.GetValue(headerProperty);
}
set
{
base.SetValue(headerProperty, value);
RaisePropertyChange("Header");
}
}
public static readonly DependencyProperty messageTypeProperty =
DependencyProperty.Register("MessageType", typeof(MessageEnumType), typeof(SummaryUserControl), new PropertyMetadata(MessageEnumType.Error, null));
public MessageEnumType MessageType
{
get { return (MessageEnumType)GetValue(messageTypeProperty); }
set { SetValue(messageTypeProperty, value); RaisePropertyChange("MessageType"); }
}
How can I change the values of the dependency properties messageType and Header? I'm unable to access those properties in either the CollectionChanged or NotifyPropertyChanged event since all those events are static. I cannot access the instance within these static event handlers.
I tried to fix the problem with a converter, but my curosity on Silverlight makes me want to use the above approach. How can I set values for those dependency properties within CollectionChanged event or NotifyPropertyChanged events?
The sender in your static fun1 method should be the instance of the class which declares the itemsProperty DependencyProperty. Therefore you can access the concrete instance with casting the sender to your class.
public static void fun1(object sender, DependencyPropertyChangedEventArgs evt)
{
MyClass concreteInstance = sender as MyClass;
if(concreateInstance != null)
{
[...your code...]
}
}

Resources