I created a assembly for my MVVM Project in WPF. In my assembly i have a Behavior for activate when I want to sort a column in a datagrid.
Behavior SortColumn
public class SortColumn : Behavior<DataGrid>
{
public string Property = "";
public bool MeaningSort = true;
public static DependencyProperty AtSortingColumnCommandProperty = DependencyProperty.RegisterAttached(
"AtSortingColumnCommand", typeof(ICommand),
typeof(SortColumn));
public static ICommand GetAtSortingColumnCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(AtSortingColumnCommandProperty);
}
public static void SetAtSortingColumnCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(AtSortingColumnCommandProperty, value);
}
protected override void OnAttached()
{
AssociatedObject.Sorting += AssociatedObject_Sorting;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.Sorting -= AssociatedObject_Sorting;
base.OnDetaching();
}
//Selon MeaningSort, on renvoie une chaine OrderBy en ASC ou DESC
//Ex: MonChamp ASC
private void AssociatedObject_Sorting(object sender, DataGridSortingEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
string FiledName = e.Column.SortMemberPath;
if (Property == null || (Property != FiledName && MeaningSort != false))
{
e.Column.SortDirection = ListSortDirection.Ascending;
MeaningSort = false;
var atEnd = GetAtSortingColumnCommand(element);
if (atEnd != null)
{
atEnd.Execute(FiledName + " ASC");
}
}
else
{
e.Column.SortDirection = ListSortDirection.Descending;
MeaningSort = true;
var atEnd = GetAtSortingColumnCommand(element);
if (atEnd != null)
{
atEnd.Execute(FiledName + " DESC");
}
}
}
}
And in my XAML
<D:DataGridTemplate x:Name="Datagrid"
TablePaged:ScrollViewerMonitor.AtEndCommand="{Binding LoadCommand}"
TablePaged:SortColumn.AtSortingColumnCommand="{Binding SortingColumnCommand}"
Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" ItemsSource="{Binding DataProduits,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
So it works fine ONLY when my file is in the same assembly. Maybe i'm not on the same Instance ?
Attaching a behavior is quite intuitive, and should be done in the following manner:
1) Include the following xmlns in the document header
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
2) Define the xmlns in which the behavior resides (Also in the document header)
xmlns:b="clr-namespace:MyAssembly.MyBehaviors;assembly=MyAssembly"
3) Attach the behavior to the element in the following manner:
<Element>
<i:Interaction.Behaviors>
<b:MyBehavior MyDependencyProperty={Binding Foo}>
</i:Interaction.Behaviors>
</Element>
As long that you follow these steps, the behavior will become attached, but mind this:
In order for the behavior to attach, the element to which the behavior is attached must be a subclass of the type specified by the behavior's generic parameter (if you are using the generic Behavior base class).
Alternatively, it must be a subclass of DependencyObject if you are using the non generic Behavior base class.
EDIT 1:
The behavior you implemented is for a DataGrid, yet your XAML attached it to the DataGridTemplate class, all this while the Attached Property is registered to your behavior instead of to some TargetType.. it really seems all over the place...
EDIT 2:
It seems that you took the attached behavior approach, which kind of makes subclassing behavior pointless..
If you already took the time to subclass Behavior, you might as well make those properties regular Dependency Properties.
thank you very much ! Thanks to your advice, I was able to correct the problem. The code following code works perfectly from another Assembly
public class SortColumn
{
public static string Property = "";
public static bool MeaningSort = true;
public static DependencyProperty AtSortingColumnCommandProperty = DependencyProperty.RegisterAttached("AtSortingColumnCommand",
typeof(ICommand), typeof(SortColumn), new PropertyMetadata(OnAtSortingColumnCommandChanged));
public static ICommand GetAtSortingColumnCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(AtSortingColumnCommandProperty);
}
public static void SetAtSortingColumnCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(AtSortingColumnCommandProperty, value);
}
public static void OnAtSortingColumnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myDataGrid = d as DataGrid;
if (myDataGrid != null)
{
myDataGrid.Sorting -= HandleColumnSorting;
myDataGrid.Sorting += HandleColumnSorting;
}
}
static void HandleColumnSorting(object sender, DataGridSortingEventArgs e)
{
var myDataGridSorting = sender as DataGrid;
string FiledName = e.Column.SortMemberPath;
if (Property == null || (Property != FiledName && MeaningSort != false))
{
e.Column.SortDirection = ListSortDirection.Ascending;
MeaningSort = false;
var atEnd = GetAtSortingColumnCommand(myDataGridSorting);
if (atEnd != null)
{
atEnd.Execute(FiledName + " ASC");
}
}
else
{
e.Column.SortDirection = ListSortDirection.Descending;
MeaningSort = true;
var atEnd = GetAtSortingColumnCommand(myDataGridSorting);
if (atEnd != null)
{
atEnd.Execute(FiledName + " DESC");
}
}
}
}
Related
I have a base class from System.Windows.Controls.Control that changes Visibility, Enabled , Background, Foreground properties according to data from outside.
when I use the class like below
public class RsdDesignBase : Button
{
....
}
It works for Button Control. I want to use same class for other controls like TextBox, Image, TextBlock but if I use like this I neet copy paste same code for all other controls.
Is there a way to use my RsdDesignBase class as base class for others controls ? Or any other way to do this.
I will paste whole class below. What it does is waits for changes in DataTag objects when they change it changes to some properties. For example if _enabledTag.Value is 0 it disables the control.
public class RsdDesignButtonBase : Button
{
private DataTag _visibilityTag;
private DataTag _enabledTag;
private DataTag _appearanceTag;
public TagScriptObject TagScriptObject { get; set; }
private readonly Timer _timer;
protected RsdDesignButtonBase()
{
Loaded += RSD_ButtonBase_Loaded;
Unloaded += OnUnloaded;
_timer = new Timer(1000);
_timer.Elapsed += TimerOnElapsed;
}
private void TimerOnElapsed(object sender, ElapsedEventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
var background = Background;
var foreground = Foreground;
Background = foreground;
Foreground = background;
}), DispatcherPriority.Render);
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (_enabledTag != null) _enabledTag.DataChanged -= EnabledTagOnDataChanged;
if (_visibilityTag != null) _visibilityTag.DataChanged -= VisibilityTagOnDataChanged;
if (_appearanceTag != null) _appearanceTag.DataChanged -= AppearanceTagOnDataChanged;
}
private void RSD_ButtonBase_Loaded(object sender, RoutedEventArgs e)
{
DependencyPropertyDescriptor desc =
DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty, typeof(FrameworkElement));
desc.AddValueChanged(this, TagPropertyChanged);
TagPropertyChanged(null, null);
}
private void TagPropertyChanged(object sender, EventArgs e)
{
if (Tag == null) return;
TagScriptObject = JsonConvert.DeserializeObject<TagScriptObject>(Tag.ToString());
if (TagScriptObject?.VisibilityProperty?.TagId > 0)
{
_visibilityTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.VisibilityProperty?.TagId);
if (_visibilityTag != null)
{
_visibilityTag.DataChanged += VisibilityTagOnDataChanged;
VisibilityTagOnDataChanged(null, null);
}
}
if (TagScriptObject?.EnableProperty?.TagId > 0)
{
_enabledTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.EnableProperty?.TagId);
if (_enabledTag != null)
{
_enabledTag.DataChanged += EnabledTagOnDataChanged;
EnabledTagOnDataChanged(null, null);
}
}
if (TagScriptObject?.AppearanceProperty?.TagId > 0)
{
_appearanceTag =
GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.AppearanceProperty?.TagId);
if (_appearanceTag != null && !_appearanceTag.IsEventHandlerRegistered(null))
{
_appearanceTag.DataChanged += AppearanceTagOnDataChanged;
AppearanceTagOnDataChanged(null, null);
}
}
}
private void AppearanceTagOnDataChanged(object source, EventArgs args)
{
_timer.Enabled = false;
_ = Dispatcher.BeginInvoke(new Action(() =>
{
double tagValue;
bool result = true;
if (_appearanceTag.VarType == VarType.Bit)
{
tagValue = _appearanceTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_appearanceTag.TagValue.ToString(), out tagValue);
}
if (result)
{
foreach (var controlColor in TagScriptObject.AppearanceProperty.ControlColors)
{
if (tagValue >= controlColor.RangeMin &&
tagValue <= controlColor.RangeMax)
{
Background =
new BrushConverter().ConvertFromString(controlColor.Background) as SolidColorBrush;
Foreground =
new BrushConverter().ConvertFromString(controlColor.Foreground) as SolidColorBrush;
_timer.Enabled = controlColor.Flashing == ConfirmEnum.Yes;
break;
}
}
}
}), DispatcherPriority.Render);
}
private void EnabledTagOnDataChanged(object source, EventArgs args)
{
_ = Dispatcher.BeginInvoke(new Action(() =>
{
if (_enabledTag != null)
{
if (TagScriptObject.EnableProperty.IsRangeSelected)
{
double tagValue;
bool result = true;
if (_enabledTag.VarType == VarType.Bit)
{
tagValue = _enabledTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_enabledTag.TagValue.ToString(), out tagValue);
}
if (result)
{
if (tagValue >= TagScriptObject.EnableProperty.RangeFrom &&
tagValue <= TagScriptObject.EnableProperty.RangeTo)
{
IsEnabled = TagScriptObject.EnableProperty.DefaultValue;
}
else
{
IsEnabled = !TagScriptObject.EnableProperty.DefaultValue;
}
}
}
else
{
if (_enabledTag.IsNumeric || _enabledTag.VarType == VarType.Bit)
{
var bitArray = _enabledTag.GetBitArray();
var singleBit = TagScriptObject.EnableProperty.SingleBit;
if (bitArray.Count > singleBit)
{
if (bitArray[singleBit])
{
IsEnabled = TagScriptObject.EnableProperty.DefaultValue;
}
else
{
IsEnabled = !TagScriptObject.EnableProperty.DefaultValue;
}
}
}
}
}
}), DispatcherPriority.Render);
}
private void VisibilityTagOnDataChanged(object source, EventArgs args)
{
_ = Dispatcher.BeginInvoke(new Action(() =>
{
if (_visibilityTag != null)
{
if (TagScriptObject.VisibilityProperty.IsRangeSelected)
{
double tagValue;
bool result = true;
if (_visibilityTag.VarType == VarType.Bit)
{
tagValue = _visibilityTag.TagValue ? 1 : 0;
}
else
{
result = double.TryParse(_visibilityTag.TagValue.ToString(), out tagValue);
}
if (result)
{
if (tagValue >= TagScriptObject.VisibilityProperty.RangeFrom &&
tagValue <= TagScriptObject.VisibilityProperty.RangeTo)
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Visible
: Visibility.Hidden;
}
else
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Collapsed
: Visibility.Visible;
}
}
}
else
{
if (_visibilityTag.IsNumeric || _visibilityTag.VarType == VarType.Bit)
{
var bitArray = _visibilityTag.GetBitArray();
var singleBit = TagScriptObject.VisibilityProperty.SingleBit;
if (bitArray.Count > singleBit)
{
if (bitArray[singleBit])
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Visible
: Visibility.Hidden;
}
else
{
Visibility = TagScriptObject.VisibilityProperty.DefaultValue
? Visibility.Hidden
: Visibility.Visible;
}
}
}
}
}
}), DispatcherPriority.Render);
}
}
If I understand you correctly, you want to add some feature to Button, TextBox, Image and TextBlock (and possibly more) and reuse that code for all classes, right?
What you're doing right now is adding a Base at the bottom of the inheritance tree. That way you can't share it with other classes. Ideally, you would want to change the System.Windows.Controls.Control, but that's part of the .NET Framework, so you can't change that...
This is the downside of inheritance...
The only possibility I see is to use composition:
Create a class containing the logic you want. Let's call it RsdDesign. No superclass needed. It will look a lot like your RsdDesignButtonBase.
Create a descendant for every Control you want to add this feature to
Give those descendants a private member of type ``RsdDesign````.
Connect all applicable methods of the Control to the member.
public class RsdDesign
{
private DataTag _visibilityTag;
private DataTag _enabledTag;
private DataTag _appearanceTag;
public TagScriptObject TagScriptObject { get; set; }
private readonly Timer _timer;
private System.Windows.Controls.Control _parentControl
protected RsdDesign(System.Windows.Controls.Control parentControl)
{
_parentControl = parentControl;
_parentControl.Loaded += RSD_ButtonBase_Loaded;
_parentControl.Unloaded += OnUnloaded;
_timer = new Timer(1000);
_timer.Elapsed += TimerOnElapsed;
}
// The rest of your RsdDesignButtonBase implementation
// ...
}
public class RsdDesignButton: Button
{
private RsdDesign _design;
public RsdDesignButton(...)
{
_design = new RsdDesign(this);
}
// You may need to hook some of the methods explicitly like this:
private void EnabledTagOnDataChanged(object source, EventArgs args)
{
_design.EnabledTagOnDataChanged(source, args);
}
}
I haven't tried this, but maybe the idea helps you to find a solution.
If you derive from your RsdDesignButtonBase class from FrameworkElement:
public class RsdDesignBase : FrameworkElement
{
...
}
...you should be able to extend and customize it for TextBox, Image, TextBlock and any other FrameworkElement, e.g.:
public class TextBlock : RsdDesignBase {}
As far as I can see your control does two(three) things:
It sets a certain layout to the control (visibility, background etc)
it deals a lot with (de)serializing and processing JSON data.
Some of the processing in return modifies UI properties (e.g. Hide/Show) if certain data is available or not.
Following the helpful principal of "separation of concerns" - not because it sound academic or is 'awesome', but because you don't get into a mess of too tightly coupled code - I would much rather recommend to put all of this logic into an Attached Property or a set of Attached properties. And to pass the control as the first argument.
You would not have to change a lot of the implementation and you could use it for virtually all WPF elements that derive from Control or even FrameworkElement
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/attached-properties-overview?view=netframeworkdesktop-4.8
I'm building a Windows Forms Application that displays a custom class Record objects and sorts them by how long they've been in my SortableBindingList<Record> record_list. When I start my program, I have some "dummy" records loaded into this list already for the sake of testing.
The SortableBindingList<T> has been taken from here.
public partial class Form1 : Form
{
public SortableBindingList<Record> record_list = new SortableBindingList<Record> { };
public static DataGridViewCellStyle style = new DataGridViewCellStyle();
public Form1()
{
InitializeComponent();
dataGridView.DataSource = record_list;
FillData(); //Temporary function to insert dummy data for demo.
dataGridView.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.cell_formatting);
this.Controls.Add(dataGridView);
this.dataGridView.RowHeadersVisible = false;
this.dataGridView.Sort(this.dataGridView.Columns["UserName"], ListSortDirection.Ascending);
start_timer();
}
Result before "new" data is added (note: this was alphabetized automatically, specifically entered into the list out of alphabetical order):
Result after data is added:
Finally, result after I click the "UserName" header:
So, must I force a sort every time my DataSource is updated? If that's the case, how do I call a sort in such a manner?
Thank you for your assistance in advance!
You need to apply sort when the list changes.
The SortableBindingList<T> needs some changes to keep the the list sorted when some changes made in list. Here is the full code with changes which I made.
Pay attention The OnListChanged method of BindingList will be called automatically after adding and removing items. But if you need to OnListChanged also runs after changing properties of items, you should implement INotifyPropertyChanged for your model class.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
public class SortableBindingList<T> : BindingList<T>
{
private bool isSortedValue;
ListSortDirection sortDirectionValue;
PropertyDescriptor sortPropertyValue;
public SortableBindingList() : base() { }
public SortableBindingList(IList<T> list) : base(list) { }
protected override void ApplySortCore(PropertyDescriptor prop,
ListSortDirection direction)
{
Type interfaceType = prop.PropertyType.GetInterface("IComparable");
if (interfaceType == null && prop.PropertyType.IsValueType)
{
Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType);
if (underlyingType != null)
{
interfaceType = underlyingType.GetInterface("IComparable");
}
}
if (interfaceType != null)
{
sortPropertyValue = prop;
sortDirectionValue = direction;
IEnumerable<T> query = base.Items;
if (direction == ListSortDirection.Ascending)
query = query.OrderBy(i => prop.GetValue(i));
else
query = query.OrderByDescending(i => prop.GetValue(i));
int newIndex = 0;
foreach (object item in query)
{
this.Items[newIndex] = (T)item;
newIndex++;
}
isSortedValue = true;
sorting = true;
this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
sorting = false;
}
else
{
throw new NotSupportedException("Cannot sort by " + prop.Name +
". This" + prop.PropertyType.ToString() +
" does not implement IComparable");
}
}
bool sorting = false;
protected override PropertyDescriptor SortPropertyCore
{
get { return sortPropertyValue; }
}
protected override ListSortDirection SortDirectionCore
{
get { return sortDirectionValue; }
}
protected override bool SupportsSortingCore
{
get { return true; }
}
protected override bool IsSortedCore
{
get { return isSortedValue; }
}
protected override void RemoveSortCore()
{
isSortedValue = false;
sortPropertyValue = null;
}
protected override void OnListChanged(ListChangedEventArgs e)
{
if (!sorting && sortPropertyValue != null)
ApplySortCore(sortPropertyValue, sortDirectionValue);
else
base.OnListChanged(e);
}
}
I have an attached property to textboxes in my view. The attached property performs validation on the textbox input and performs other chores. The attached property validation routine raises an event which is being watched by the viewmodel.
Does this "violate" MVVM reasoning by having the viewmodel obtain the invalid TextBoxes?
How will the GC deal with the static events from the attached property when the usercontrol containing the textboxes is removed?
If specific code is needed to avoid memory leaks, how is that done?
Is there a preferred way to do this?
Sorry for the long list, but Google does not address this situation.
Any and all help is appreciated. Thank you for your consideration.
(VS2010 .net 4.5)
TIA
ViewModel
class CheckInViewModel : SimpleViewModelBase
{
public CheckInViewModel()
{
InValidTextBoxes = new List<TextBox>();
Stargate_V.Helpers.ColorMaskingTextBoxBehavior.Validated += (sender, e) =>
{
if (e.valid)
InValidTextBoxes.Remove(e.sender);
else
InValidTextBoxes.Add(e.sender);
};
}
List<TextBox> InValidTextBoxes;
}
XAML
<TextBox
h:ColorMaskingTextBoxBehavior.Mask="^[MmFf]$"
Text="{Binding Sex}"
Height="24" HorizontalAlignment="Right" Margin="0,55,665,0" VerticalAlignment ="Top" Width="36" />
Attached Properity
public class ColorMaskingTextBoxBehavior : DependencyObject
{
// Entrance point from Xaml
public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached("Mask",
typeof(string),
typeof(ColorMaskingTextBoxBehavior),
new FrameworkPropertyMetadata(OnMaskChanged));
...........................
// Callback from XAML initialization of the attached property.
private static void OnMaskChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var textBox = dependencyObject as TextBox;
var mask = e.NewValue as string;
textBox.PreviewTextInput -= textBox_PreviewTextInput;
textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
DataObject.RemovePastingHandler(textBox, Pasting);
DataObject.RemoveCopyingHandler(textBox, NoDragCopy);
CommandManager.RemovePreviewExecutedHandler(textBox, NoCutting);
if (mask == null)
{
textBox.ClearValue(MaskProperty);
textBox.ClearValue(MaskExpressionProperty);
}
else
{
textBox.SetValue(MaskProperty, mask);
SetMaskExpression(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace));
textBox.PreviewTextInput += textBox_PreviewTextInput;
textBox.PreviewKeyDown += textBox_PreviewKeyDown;
DataObject.AddPastingHandler(textBox, Pasting);
DataObject.AddCopyingHandler(textBox, NoDragCopy);
CommandManager.AddPreviewExecutedHandler(textBox, NoCutting);
}
}
private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
var textBox = sender as TextBox;
var maskExpression = GetMaskExpression(textBox);
string passHex = (string)textBox.GetValue(PassColorProperty);
string failHex = (string)textBox.GetValue(FailColorProperty);
Color passColor = Extensions.ToColorFromHex(passHex);
Color failColor = Extensions.ToColorFromHex(failHex);
if (maskExpression == null)
{
return;
}
var proposedText = GetProposedText(textBox, e.Text);
if (!maskExpression.IsMatch(proposedText))
{
textBox.Background = new SolidColorBrush(failColor);
ValidationEventArgs args = new ValidationEventArgs();
args.sender = textBox;
args.valid = false;
OnValidation(args);
}
else
{
textBox.Background = new SolidColorBrush(passColor);
ValidationEventArgs args = new ValidationEventArgs();
args.sender = textBox;
args.valid = true;
OnValidation(args);
}
}
Event Called from the above code
public static event EventHandler<ValidationEventArgs> Validated;
static void OnValidation(ValidationEventArgs e)
{
EventHandler<ValidationEventArgs> handler = Validated;
if (handler != null)
{
handler(null, e);
}
}
public class ValidationEventArgs : EventArgs
{
public TextBox sender;
public bool valid;
}
Yes, I would argue that this violates MVVM. Your view model should have no knowledge of the views whatsoever. The question to always ask yourself is "can I run my application without creating any views?". In this case your view model is interacting directly with a list of TextBoxes, so the pattern is broken.
There are several ways of achieving your goal here, probably the most simple is to create a handler in your view model that gets called when your TextBox text changes:
public delegate void ValidationDelegate(bool isValid);
public class MyViewModel : ViewModelBase
{
public ValidationDelegate ValidationHandler { get { return (isValid) => OnValidate(isValid); } }
private void OnValidate(bool isValid)
{
// handle the validation event here
}
}
Now all you need is a behavior with an attached property that you can bind to this handler:
public class ValidateBehavior : Behavior<TextBox>
{
public ValidationDelegate Validated
{
get { return (ValidationDelegate)GetValue(ValidatedProperty); }
set { SetValue(ValidatedProperty, value); }
}
public static readonly DependencyProperty ValidatedProperty =
DependencyProperty.Register("Validated", typeof(ValidationDelegate), typeof(ValidateBehavior), new PropertyMetadata(null));
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged += ValidateText;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.TextChanged -= ValidateText;
}
private void ValidateText(object sender, TextChangedEventArgs e)
{
if (this.Validated != null)
{
bool isValid = true; // do text validation here
this.Validated(isValid);
}
}
}
And then finally add the behaviour to the TextBox in question and bind the handler:
<TextBox>
<i:Interaction.Behaviors>
<behaviors:ValidateBehavior Validated="{Binding ValidationHandler}"/>
</i:Interaction.Behaviors>
</TextBox>
EDIT: If you don't want to use a Blend behaviour then you can also do it with an attached behaviour:
public static class ValidateBehavior
{
public static ValidationDelegate GetValidate(TextBox textbox)
{
return (ValidationDelegate)textbox.GetValue(ValidateProperty);
}
public static void SetValidate(TextBox textbox, ValidationDelegate value)
{
textbox.SetValue(ValidateProperty, value);
}
public static readonly DependencyProperty ValidateProperty =
DependencyProperty.RegisterAttached(
"Validate",
typeof(ValidationDelegate),
typeof(ValidateBehavior),
new UIPropertyMetadata(null, OnValidateChanged));
static void OnValidateChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var textbox = depObj as TextBox;
if (textbox == null)
return;
if (e.OldValue is ValidationDelegate)
textbox.TextChanged -= OnTextChanged;
if (e.NewValue is ValidationDelegate)
textbox.TextChanged += OnTextChanged;
}
static void OnTextChanged(object sender, RoutedEventArgs e)
{
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
var textbox = e.OriginalSource as TextBox;
if (textbox != null)
{
var validate = GetValidate(textbox);
if (validate != null)
{
bool isValid = true; // do text validation here
validate(isValid);
}
}
}
}
And the corresponding XAML:
<TextBox behaviors:ValidateBehavior.Validate="{Binding ValidationHandler}" />
I have a base DependencyObject class where I have a method that takes an object, gets the properties, and for each property that is a type that implements INotifyPropertyChanged, I add a new PropertyChangedEventHandler. Now in the handler method, it gets the parameters of an object "sender" and the PropertyChangedEventArgs "e". My question is, does anyone know how to dynamically get the property name if sender is a property of a type that implements the INotifyPropertyChanged.
Here is what I'm working with:
public class BaseDependencyObject : DependencyObject, INotifyPropertyChanged
{
public BaseDependencyObject()
{
}
protected void SetValues(Object thisObject, Object entity)
{
try
{
PropertyInfo[] properties = entity.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
var value = property.GetValue(entity, null);
var valueIsEntity = value is System.ServiceModel.DomainServices.Client.Entity;
var thisObjectsProperty = thisObject.GetType().GetProperty(property.Name);
if (thisObjectsProperty != null && value != null)
{
if (valueIsEntity)
{
if (thisObjectsProperty.PropertyType.GetInterface("INotifyPropertyChanged", true) != null)
{
var propertyInstance = Activator.CreateInstance(thisObjectsProperty.PropertyType);
((INotifyPropertyChanged)propertyInstance).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
}
SetValues(thisObjectsProperty, value);
}
else if (thisObjectsProperty.PropertyType.GetInterface("ICollection", true) != null
&& thisObjectsProperty.PropertyType.GetGenericArguments().Count() > 0)
{
Type genericType = thisObjectsProperty.PropertyType.GetGenericArguments()[0];
var observableCollection = Activator.CreateInstance(thisObjectsProperty.PropertyType) as IList;
if (observableCollection is INotifyCollectionChanged)
((INotifyCollectionChanged)observableCollection).CollectionChanged += this.Object_CollectionChanged;
if (observableCollection is INotifyPropertyChanged)
((INotifyPropertyChanged)observableCollection).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
foreach (var item in (IEnumerable)value)
{
var newItem = Activator.CreateInstance(genericType);
if (newItem != null)
{
SetValues(newItem, item);
observableCollection.Add(newItem);
}
}
}
else
{
thisObjectsProperty.SetValue(thisObject, value, null);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
protected void Object_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged -= this.Object_PropertyChanged;
}
}
break;
}
}
protected void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.NotifyPropertyChanged(e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The SetValues method's first param is the DependencyObject type that will be used in the view model. The second param is the entity that is being returned from the DomainService's Context.LoadOperation.
What my issue boils down to is when the INotifyCollectionChanged.CollectionChanged fires I'm needing to be able to raise the PropertyChanged event with the collection's property name. So if anyone has any advise I would greatly appreciate it. Thanks in advance.
Edit
Figured out how to get the properties name that is firing the event. Here is an edited version of my PropertyChangedEventHandler.
protected void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var properties = this.GetType().GetProperties().Where(x => x.PropertyType == sender.GetType()).ToArray();
foreach (var property in properties)
{
this.NotifyPropertyChanged(property.Name);
}
//this.NotifyPropertyChanged(e.PropertyName);
}
Basically this does what I was looking for, but aparentyly I am still not doing something right. The UIElement is still not updating when the ObservableCollection that is a property of another type is being added to.
Here is an example of my DependencyObjects and ViewModel:
public class LOB : DependencyObject
{
public Int32 ID
{
get { return (Int32)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
NotifyPropertyChanged("ID");
}
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(Int32), typeof(LOB), null);
public ObservableCollection<Group> Groups
{
get { return (ObservableCollection<Group>)GetValue(GroupsProperty); }
set
{
SetValue(GroupsProperty, value);
NotifyPropertyChanged("Groups");
}
}
public static readonly DependencyProperty GroupsProperty =
DependencyProperty.Register("Groups", typeof(ObservableCollection<Group>), typeof(LOB), new PropertyMetadata(null, OnGroupsPropertyChanged));
static void OnGroupsPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged += new NotifyCollectionChangedEventHandler(((LOB)obj).Object_CollectionChanged);
((INotifyPropertyChanged)e.NewValue).PropertyChanged += new PropertyChangedEventHandler(((LOB)obj).Object_PropertyChanged);
}
if (e.OldValue != null)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -= ((LOB)obj).Object_CollectionChanged;
((INotifyPropertyChanged)e.OldValue).PropertyChanged -= ((LOB)obj).Object_PropertyChanged;
}
}
}
public class Group : DependencyObject
{
public Int32 ID
{
get { return (Int32)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
NotifyPropertyChanged("ID");
}
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(Int32), typeof(Group), null);
public String GroupName
{
get { return (String)GetValue(GroupNameProperty); }
set
{
SetValue(GroupNameProperty, value);
NotifyPropertyChanged("GroupName");
}
}
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.Register("GroupName", typeof(String), typeof(Group), null);
}
public class MyViewModel : DependencyObject
{
public static readonly DependencyProperty LobCollectionProperty =
DependencyProperty.Register("LobCollection",
typeof(ObservableCollection<LOB>),
typeof(MyViewModel),
new PropertyMetadata(null, LobCollectionPropertyChanged));
public ObservableCollection<LOB> LobCollection
{
get { return (ObservableCollection<MainBusinessLine>)GetValue(LobCollectionPropertyChanged); }
set
{
SetValue(MainBusinessLineCollectionProperty, value);
NotifyPropertyChanged("LobCollection");
}
}
static void LobCollectionPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var viewModel = obj as MyViewModel;
if (viewModel == null)
return;
if (e.OldValue != null)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -= viewModel.LobCollection_Changed;
}
if (e.NewValue != null)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged += viewModel.LobCollection_Changed;
}
}
void LobCollection_Changed(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("LobCollection");
}
}
After our conversation above, this is rather moot, but I thought about how I'd implement a base class that fired PropertyChanged events when a collection changed in a property that was defined by the subclass. As I said, it's a bit non-standard, but here's how I'd do it.
class FancyCollectionAndPropertyChangedBase : INotifyPropertyChanged
{
private Dictionary<ICollectionChanged, String> collectionNameLookup = new Dictionary<ICollectionChanged, String>();
protected FancyCollectionAndPropertyChangedBase()
{
this.PropertyChanged += MyPropertyChanged;
}
private void MyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(this.collectionNameLookup.ContainsValue(e.PropertyName)
{
KeyValuePair<INotifyCollectionChanged, String> oldValue = this.collectionNameLookup.First(kvp => kvp.Value == e.Name);
oldValue.Key -= MyCollectionChanged;
this.collecitonNameLookup.Remove(oldValue.Key);
INotifyCollectionChanged collection = this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).GetValue(this, null);
collection.CollectionChanged += MyCollectionChanged;
this.collectionNameLookup.Add(collection, e.Name);
}
else if(typeof(INotifyCollectionChanged).IsAssignableFrom(this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).PropertyType))
{
// Note: I may have gotten the IsAssignableFrom statement, above, backwards.
INotifyCollectionChanged collection = this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).GetValue(this, null);
collection.CollectionChanged += MyCollectionChanged;
this.collectionNameLookup.Add(collection, e.Name);
}
}
private void MyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.NotifyPropertyChanged(this.collectionNameLookup[sender];
}
}
I am trying to use the Silverlight 3.0 DataGrid with the MVVM design pattern. My page has a DataGrid and a button that adds an item to the collection in the VM using a command (from the Composite Application Library). This works fine, and the new item is displayed and selected.
The problem I can't solve is how to begin editing the row. I want the new row to be immediately editable when the user clicks the Add button i.e. focus set to the DataGrid and the new row in edit mode.
This is the XAML in the view:
<Grid x:Name="LayoutRoot">
<StackPanel>
<data:DataGrid ItemsSource="{Binding DataView}"/>
<Button cmd:Click.Command="{Binding AddItemCommand}" Content="Add" />
</StackPanel>
</Grid>
The code behind has one line of code that creates an instance of the VM and sets the DataContext of the view.
The VM code is:
public class VM
{
public List<TestData> UnderlyingData { get; set; }
public PagedCollectionView DataView { get; set; }
public ICommand AddItemCommand { get; set; }
public VM()
{
AddItemCommand = new DelegateCommand<object>(o =>
{
DataView.AddNew();
});
UnderlyingData = new List<TestData>();
UnderlyingData.Add(new TestData() { Value = "Test" });
DataView = new PagedCollectionView(UnderlyingData);
}
}
public class TestData
{
public string Value { get; set; }
public TestData()
{
Value = "<new>";
}
public override string ToString()
{
return Value.ToString();
}
}
What would be the best way to solve this problem using the MVVM design pattern?
I faced the same issue. I've introduced interface ISupportEditingState:
public interface ISupportEditingState
{
EditingState EditingState { get; set; }
}
My VM implements it. And then I wrote this behaviour to synchronise editing state of DataGrid and my VM:
public class SynchroniseDataGridEditingStateBehaviour : Behavior<DataGrid>
{
public static readonly DependencyProperty EditingStateBindingProperty =
DependencyProperty.Register("EditingStateBinding", typeof(ISupportEditingState),
typeof(SynchroniseDataGridEditingStateBehaviour), new PropertyMetadata(OnEditingStateBindingPropertyChange));
private bool _attached;
private bool _changingEditingState;
public ISupportEditingState EditingStateBinding
{
get { return (ISupportEditingState)GetValue(EditingStateBindingProperty); }
set { SetValue(EditingStateBindingProperty, value); }
}
private static void OnEditingStateBindingPropertyChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var b = d as SynchroniseDataGridEditingStateBehaviour;
if (b == null)
return;
var oldNotifyChanged = e.OldValue as INotifyPropertyChanged;
if (oldNotifyChanged != null)
oldNotifyChanged.PropertyChanged -= b.OnEditingStatePropertyChanged;
var newNotifyChanged = e.NewValue as INotifyPropertyChanged;
if (newNotifyChanged != null)
newNotifyChanged.PropertyChanged += b.OnEditingStatePropertyChanged;
var newEditingStateSource = e.NewValue as ISupportEditingState;
if (newEditingStateSource.EditingState == EditingState.Editing)
{
// todo: mh: decide on this behaviour once again.
// maybe it's better to start editing if selected item is already bound in the DataGrid
newEditingStateSource.EditingState = EditingState.LastCancelled;
}
}
private static readonly string EditingStatePropertyName =
CodeUtils.GetPropertyNameByLambda<ISupportEditingState>(ses => ses.EditingState);
private void OnEditingStatePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_changingEditingState || !_attached || e.PropertyName != EditingStatePropertyName)
return;
_changingEditingState = true;
var editingStateSource = sender as ISupportEditingState;
if (editingStateSource == null)
return;
var grid = AssociatedObject;
var editingState = editingStateSource.EditingState;
switch (editingState)
{
case EditingState.Editing:
grid.BeginEdit();
break;
case EditingState.LastCancelled:
grid.CancelEdit();
break;
case EditingState.LastCommitted:
grid.CommitEdit();
break;
default:
throw new InvalidOperationException("Provided EditingState is not supported by the behaviour.");
}
_changingEditingState = false;
}
protected override void OnAttached()
{
var grid = AssociatedObject;
grid.BeginningEdit += OnBeginningEdit;
grid.RowEditEnded += OnEditEnded;
_attached = true;
}
protected override void OnDetaching()
{
var grid = AssociatedObject;
grid.BeginningEdit -= OnBeginningEdit;
grid.RowEditEnded -= OnEditEnded;
_attached = false;
}
void OnEditEnded(object sender, DataGridRowEditEndedEventArgs e)
{
if (_changingEditingState)
return;
EditingState editingState;
if (e.EditAction == DataGridEditAction.Commit)
editingState = EditingState.LastCommitted;
else if (e.EditAction == DataGridEditAction.Cancel)
editingState = EditingState.LastCancelled;
else
return; // if DataGridEditAction will ever be extended, this part must be changed
EditingStateBinding.EditingState = editingState;
}
void OnBeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
if (_changingEditingState)
return;
EditingStateBinding.EditingState = EditingState.Editing;
}
}
Works ok for me, hope it helps.
Whenever you talk about directly accessing ui components, your kinda missing the point of mvvm. The ui binds to the viewmodel, so find a way to alter the viewmodel instead.