I am using the following articles to validate inputs from the user:
http://weblogs.asp.net/monikadyrda/archive/2009/06/24/wpf-textbox-validation.aspx
http://weblogs.asp.net/monikadyrda/archive/2009/07/28/wpf-textbox-validation-2.aspx
I have a window with 100+ textbox, and I need to check with all textboxes are valid.
Imagine the following situation -> User has inputted an invalid value:
1) The validation rule will verify the error and display an error
message (Good!)
2) The viewmodel will not know that the value has
been updated (with a invalid string). It will remain with its old
value.
3) Thus, any attempt to bind a "save" button enabled to an IsValid
property will fail. (as described in article 2) (Bad!)
So, my question is: How can I validate the whole page in the view model?
There's a very good article here dealing with exactly this problem -- I used this approach, and it works nicely.
The basic idea is to use an attached property -- call it "ValidationScope.Errors" -- to bind the view's validation scope to a property in your view-model.
Here's the code, quoted from the linked article:
public class ValidationScope
{
public static IList GetErrors(DependencyObject obj)
{
return (IList)obj.GetValue(ErrorsProperty);
}
public static void SetErrors(DependencyObject obj, IList value)
{
obj.SetValue(ErrorsProperty, value);
}
public static readonly DependencyProperty ErrorsProperty =
DependencyProperty.RegisterAttached("Errors", typeof(IList), typeof(ValidationScope),
new PropertyMetadata(null, ErrorsChanged));
public static void ErrorsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
FrameworkElement element = (FrameworkElement)obj;
element.BindingValidationError += delegate(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
{
GetErrors(obj).Add(e.Error);
}
else
{
GetErrors(obj).Remove(e.Error);
}
};
}
}
You can see this attached dependency property works by listening to the framework's BindingValidationError event, and adding/removing errors to the view-model target you specify. To use this in your code, simply bind the dependency property ValidationScope.Errors to a target property in your view model:
<my:SomeUserControl my:ValidationScope.Errors="{Binding MyViewModel.Errors}" />
Now your view-model contains a property "Errors" that you can use to check whether the view is actually valid or not.
Related
I have a ListBox in my view, bound to a collection that is dynamically growing. I would like the scroll position to follow the last added item (which is appended to the bottom of the list). How can I achieve this with Caliburn.Micro?
An alternative could be to use the event aggregator to publish a message to the view.
Something like:
Aggregator.Publish(ItemAddedMessage<SomeItemType>(itemThatWasJustAdded));
and in the view:
public class SomeView : IHandle<ItemAddedMessage<SomeItemType>>
{
public void Handle(ItemAddedMessage<SomeItemType> message)
{
// Implement view specific behaviour here
}
}
It depends on what your requirements are but at least then the view is responsible for display concerns and you can still test the VM
Also you could just implement the code solely in the view - since it appears to be a view concern (e.g. using the events that listbox provides)
A behaviour would also be useful but maybe one that's a little less coupled to your types - e.g. a generic behaviour SeekAddedItemBehaviour which hooks listbox events to find the last item. Not sure if the listbox exposes the required events, but worth a look
EDIT:
Ok this may work full stop - you should be able to just attach this behaviour to the listbox and it should take care of the rest:
public class ListBoxSeekLastItemBehaviour : System.Windows.Interactivity.Behavior<ListBox>
{
private static readonly DependencyProperty ItemsSourceWatcherProperty = DependencyProperty.Register("ItemsSourceWatcher", typeof(object), typeof(ListBoxSeekLastItemBehaviour), new PropertyMetadata(null, OnItemsSourceWatcherPropertyChanged));
private ListBox _listBox = null;
private static void OnItemsSourceWatcherPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBoxSeekLastItemBehaviour source = d as ListBoxSeekLastItemBehaviour;
if (source != null)
source.OnItemsSourceWatcherPropertyChanged();
}
private void OnItemsSourceWatcherPropertyChanged()
{
// The itemssource has changed, check if it raises collection changed notifications
if (_listBox.ItemsSource is INotifyCollectionChanged)
{
// if it does, hook the CollectionChanged event so we can respond to items being added
(_listBox.ItemsSource as INotifyCollectionChanged).CollectionChanged += new NotifyCollectionChangedEventHandler(ListBoxSeekLastItemBehaviour_CollectionChanged);
}
}
void ListBoxSeekLastItemBehaviour_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 0)
{
// If an item was added seek it
ScrollIntoView(e.NewItems[0]);
}
}
protected override void OnAttached()
{
base.OnAttached();
// We've been attached - get the associated listbox
var box = this.AssociatedObject as ListBox;
if (box != null)
{
// Hold a ref
_listBox = box;
// Set a binding to watch for property changes
System.Windows.Data.Binding binding = new System.Windows.Data.Binding("ItemsSource") { Source = _listBox; }
// EDIT: Potential bugfix - you probably want to check the itemssource here just
// in case the behaviour is applied after the original ItemsSource binding has been evaluated - otherwise you might miss the change
OnItemsSourceWatcherPropertyChanged();
}
}
private void ScrollIntoView(object target)
{
// Set selected item and try and scroll it into view
_listBox.SelectedItem = target;
_listBox.ScrollIntoView(target);
}
}
You probably want to tidy it up a bit and also make sure that the event handler for CollectionChanged is removed when the ItemsSource changes.
Also you might want to call it SeekLastAddedItemBehaviour or SeekLastAddedItemBehavior - I tend to keep the US spelling since it matches Microsoft's spelling. I think SeekLastItem sounds like it will scroll to the last item in the list rather than the last added item
You could reference the view in the view model using GetView(). That also couples the view and view model.
var myView = GetView() as MyView;
myView.MyListBox.DoStuff
Another option is to create a behavior. This is an example of how to use a behavior to expand a TreeView from the view model. The same could be applied to a ListBox.
Actually, there is an easier way to achieve this, without any of the above.
Just extend your Listbox with the following:
namespace Extensions.Examples {
public class ScrollingListBox : ListBox
{
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
int newItemCount = e.NewItems.Count;
if (newItemCount > 0)
this.ScrollIntoView(e.NewItems[newItemCount - 1]);
base.OnItemsChanged(e);
}
}
}
}
Then in Xaml, Declare the Location of your extension class as so:
xmlns:Extensions="clr-namespace:Extensions.Examples"
And when you create your listbox, instead of using
<Listbox></Listbox>
Just use your extended class
<Extensions:ScrollingListBox></Extensions:ScrollingListBox>
I am trying to create a Custom control derived from a standard Grid.
I added a ObservableCollection as a DependencyProperty of the Custom control. However, the get/set of it is never reached. Can I have some guidelines in creating a DependencyProperty that works correctly with and ObservableCollection?
public class MyGrid : Grid
{
public ObservableCollection<string> Items
{
get
{
return (ObservableCollection<string>)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
public static DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<string>),
typeof(MyGrid), new UIPropertyMetadata(null, OnItemsChanged));
}
I would suggest not to use ObservableCollection as the type of an Items dependency property.
The reason for having an ObservableCollection here (I guess) is to enable the UserControl to attach a CollectionChanged handler when the property value is assigned. But ObservableCollection is too specific.
The approach in WPF (e.g. in ItemsControl.ItemsSource) is to define a very basic interface type (like IEnumerable) and when the property is assigned a value, find out if the value collection implements certain more specific interfaces. This would at least be INotifyCollectionChanged here, but the collection might also implement ICollectionView and INotifyPropertyChanged. All these interfaces wouldn't be mandatory and that would enable your dependency property to bind to all sorts of collections, starting with a plain array up to a complex ItemCollection.
Your OnItemsChanged property change callback would then look like this:
private static void OnItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyGrid grid = obj as MyGrid;
if (grid != null)
{
var oldCollectionChanged = e.OldValue as INotifyCollectionChanged;
var newCollectionChanged = e.NewValue as INotifyCollectionChanged;
if (oldCollectionChanged != null)
{
oldCollectionChanged.CollectionChanged -= grid.OnItemsCollectionChanged;
}
if (newCollectionChanged != null)
{
newCollectionChanged.CollectionChanged += grid.OnItemsCollectionChanged;
// in addition to adding a CollectionChanged handler
// any already existing collection elements should be processed here
}
}
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// handle collection changes here
}
The WPF binding mechanism may bypass your standard CLR property and go directly to the dependency property accessors (GetValue and SetValue).
That is why logic should not be placed inside of the CLR property, but instead inside a changed handler.
Also the ObservableCollection<string> will never be set because when you use collection properties from XAML, like the following:
<local:MyGrid>
<local:MyGrid.Items>
<sys:String>First Item</sys:String>
<sys:String>Second Item</sys:String>
</local:MyGrid.Items>
</local:MyGrid>
It is actually calling a get on Items and then calling Add for each of the elements.
Alternatively: How to subscribe to the PropertyChanged event defined by INotifyPropertyChanged thru the databinding of two dependency properties?
I have two separate user controls inside my main window. One control contains the parameters that affect the other control, let’s call it the display control. I want the parameter control to act as the datasource of the display control so that when I change a parameter in the parameter control, the display control be listening and reacts accordingly.
For this I created a class that implements INotifyPropertyChanged that stores these parameters and created dependencies properties of this class type in both controls. I was expecting that if I binded one control property to the other I would get the desired behaviour, but unfortunately I am missing something important because the display control is not reacting.
On a closer inspection with the debugger, I notice that my event PropertyChangedEventHandler PropertyChanged was always null when a property had changed, and everything I have read indicates, that no one is listening.
Because the display control is created in real time, I have to create the binding programmatically like this:
var DispayControlValuesBinding = new Binding();
DispayControlValuesBinding.Source = DisplayControlsControl;
DispayControlValuesBinding.Path = new PropertyPath("DisplayControlValues");
DispayControlValuesBinding.Mode = BindingMode.OneWay;
DispayControlValuesBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
DispayControlValuesBinding.NotifyOnSourceUpdated = true;
//
graph.SetBinding(Graph.DisplayControlValuesProperty, DisplayControlValuesBinding);
Both controls have a dependency property called DispayControlValues. I try to bind the DisplayControlControl's DisplayControlValues property to the graph control's DisplayControlValues property.
When the application runs, it initializes the parameter control, then with a user request a display control is created programmatically and the binding is made. Then I change a value in the parameter control, this is catch by the parameters class that implements the INotifyPropertyChanged interface but because no one is listening, the event handler is null and here is where I am stuck.
Your help is greatly appreciated!
Here are more details as requested:
I have one user control that exposes the parameters that changes the behaviour of another control. This control has a dependency property that contains parameter details and implements the INotifyPropertyChanged interface.
Here is the class:
public class ZoomGraphControlValues : INotifyPropertyChanged
{
private bool _displayRaw;
public bool DisplayRaw
{
get { return _displayRaw; }
set
{
_displayRaw = value;
OnPropertyChanged(new PropertyChangedEventArgs("DisplayRaw"));
}
}
private bool _enableFit;
public bool EnableFit
{
get { return _enableFit; }
set
{
_enableFit = value;
OnPropertyChanged(new PropertyChangedEventArgs("EnableFit"));
}
}
public ZoomGraphControlValues()
{}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
Here is the dependency property:
public ZoomGraphControlValues ControlValues
{
get { return (ZoomGraphControlValues)GetValue(ControlValuesProperty); }
set { SetValue(ControlValuesProperty, value); }
}
public static readonly DependencyProperty ControlValuesProperty =
DependencyProperty.Register("ControlValues", typeof(ZoomGraphControlValues), typeof(ZoomGraphControls), new PropertyMetadata(null, OnControlValuesPropertyChanged));
private static void OnControlValuesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObj = d as ZoomGraphControls;
myObj.OnControlValuesPropertyChanged(e);
}
private void OnControlValuesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (ControlValues != null)
{
IniValues();
}
}
Then I have the display user control. This control also implements a dependency property of the same type as the other control and I want this control to be the target of the binding, so that when I change values in the parameter control, this control reflect the changes.
Here is the dependency property of this control:
public ZoomGraphControlValues ZoomGraphControlValues
{
get { return (ZoomGraphControlValues)GetValue(ZoomGraphControlValuesProperty); }
set { SetValue(ZoomGraphControlValuesProperty, value); }
}
public static readonly DependencyProperty ZoomGraphControlValuesProperty =
DependencyProperty.Register("ZoomGraphControlValues", typeof(ZoomGraphControlValues), typeof(zoomGraph), new PropertyMetadata(null, OnZoomGraphControlValuesPropertyChanged));
private static void OnZoomGraphControlValuesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObj = d as zoomGraph;
myObj.OnZoomGraphControlValuesPropertyChanged(e);
}
private void OnZoomGraphControlValuesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (ZoomGraphControlValues != null)
{
// update the control with the new parameters
ShowRawData(ZoomGraphControlValues.DisplayRaw);
SetChartBehabiour();
}
}
The Parameters control is initialized since the beginning of the application cycle. The display control gets created as per user request into a tab, so I have to create the control programmatically and thereby the binding as well:
//create the tab and wire tab events
//…
//create a display control
var graph = new zoomGraph();
// initialize the parameters class
var zgcv = new ZoomGraphControlValues
{
DisplayRaw = true,
ChartBehaviour = ChartBehaviour.Zoom
};
//assign the parameters class to the parameters user control dependency property
ZoomGraphControlsControl.ControlValues = zgcv;
//create the binding of the parameter control to the display control by linking their respective dependency properties
var zoomGraphControlValuesBinding = new Binding();
zoomGraphControlValuesBinding.Source = ZoomGraphControlsControl;
zoomGraphControlValuesBinding.Path = new PropertyPath("ControlValues");
zoomGraphControlValuesBinding.Mode = BindingMode.TwoWay;
zoomGraphControlValuesBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
zoomGraphControlValuesBinding.NotifyOnSourceUpdated = true;
zoomGraphControlValuesBinding.NotifyOnTargetUpdated = true;
graph.SetBinding(zoomGraph.ZoomGraphControlValuesProperty, zoomGraphControlValuesBinding);
//…
// add the user control to a tab
When I change a parameter in the parameter control I can see that it tries to fire the OnPropertyChanged event but it is always null. Because of this I think I am lacking something.
You are setting the binding mode to "OneWay" which means the view model will never get updated when the value changes in the view. Change the Binding mode to "TwoWay" and try again.
Also, check if you are changing the complete instance of "DisplayControlValues" or just properties on that class, because your binding is only set to fire when the entire instance changes, not its properties.
In addition to that, keep in mind that you can bind properties of two different controls using the Binding.ElementName property, which would make it unnecessary for you to create a view model, unless there is anything in the code behind you need to do when these values change.
If you post more code and XAML it will be easier to find the most appropriate way to solve your issue.
I have a need to create an attached property for a TextBox, that enforces a rule that content is required.
NOTE: Unfortunately I am not able to use data annotations, or SL4 validation frameworks.
The textboxes are displayed within the context of a View. The View is reused in many places. When tabbing / clicking between TextBoxes within the view I want a popup message to notify the user if they have left a 'Required' TextBox empty.
Now, I have this working via the LostFocus event:
public static readonly DependencyProperty RequiredProperty =
DependencyProperty.RegisterAttached("Required", typeof(bool), typeof(TextBoxRequiredService),
new PropertyMetadata(OnRequiredChanged));
public static bool GetRequired(DependencyObject d)
{
return (bool)d.GetValue(RequiredProperty);
}
public static void SetRequired(DependencyObject d, bool value)
{
d.SetValue(RequiredProperty, value);
}
private static void OnRequiredChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox textBox = d as TextBox;
textBox.LostFocus += (s, args) => {
if (textBox.Text.Length == 0) {
MessageBox.Show("Required Field!");
textBox.Focus();
}
};
}
But this is getting triggered, obvioulsy, on every lost focus, and there are certain situations, for example closing the view, that i don't want the validation to execute.
So, does anyone have any good suggestions (or examples) on a way to get a Required Text Box service working within a definable scope of actions? Or perhaps some clever alternatives to LostFocus that I could use?
Thanks,
Mark
I've got a custom control which has a DependencyProperty MyAnimal - I'm binding an Animal Property on my ViewModel to the MyAnimal DependencyProperty.
I've stuck a TextBox on the Control so I can trigger an Event - whenever I trigger the event the MyAnimal property has been set - however if I put a break point on the Setter of the MyAnimal property it never gets fired!
I guess I'm missing something fundamental about WPF Dependency Properties/Binding?!
And so my question is, if I can't use the Setter how can I find out when its been set? If I put if I put a break point after InitializeComponent() its null and I had a look to see if theres an Event a can hook up to - DatabindingFinished or similar? but can't see what it would be ...
Can anyone assist please?
Thanks,
Andy
public partial class ControlStrip
{
public ControlStrip()
{
InitializeComponent();
}
public Animal MyAnimal
{
get
{
return (Animal)GetValue(MyAnimalProperty);
}
set
{
SetValue(MyAnimalProperty, value);
}
}
public static readonly DependencyProperty MyAnimalProperty =
DependencyProperty.RegisterAttached("MyAnimal", typeof (Animal), typeof (ControlStrip));
private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
var myAnimal = MyAnimal;
MessageBox.Show(myAnimal.Name);
}
}
The setter methods are never called by the runtime. They go directly to the DependencyProperty. You will need to add an additional argument to your call to RegisterAttached(). There you can add a PropertyChangedCallback.
Here is some sample code:
public static readonly DependencyProperty MyAnimalProperty =
DependencyProperty.RegisterAttached("MyAnimal", typeof (Animal), typeof (ControlStrip), new PropertyMetadata(AnimalChanged));
private static void AnimalChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// Do work here
}
The setter is only there for your use - you actually can leave the property off entirely, since DataBinding uses the actual DependencyProperty itself, not the CLR property.
If you need to see when the property changes, you will need to specify PropertyMetadata on your dependency property, and provide a PropertyChangedCallback.
For details, I recommend reading Dependency Property Metadata.