I'm trying to get WPF Slider value after the user finishing to drag the thumb or clicking to move to specific point.
i want to save the new value in db by listening to some kind of event. how can i do this?
i tried the solutions on this question but i end up with nothing -
WPF: Slider with an event that triggers after a user drags
each time it enters the event lots of times
thanks.
xaml:
<Slider Value="{Binding VoipVolume}" MouseLeftButtonUp="slider_MouseLeftButtonUp"/>
codebehind:
public double VoipVolume
{
get { return (double)GetValue(VoipVolumeProperty); }
set
{
SetValue(VoipVolumeProperty, value);
VolumeChanged(value);
}
}
private void VolumeChanged(object value)
{
StationViewModel viewModel = this.DataContext as StationViewModel;
if (viewModel != null)
{
if (end)
{
viewModel.OnVolumeChange((float)VoipVolume);
end = false;
}
}
}
bool end = false;
private void slider_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
end = true;
}
Possibly duplicate. hope this may help you out: stackoverflow.com/a/723547/1611490
<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
and under MySlider_DragCompleted event, update your property of the VM or get the value.
if your are using FW 4.5 or above, try using the "Dalay" property.
http://www.jonathanantoine.com/2011/09/21/wpf-4-5-part-4-the-new-bindings-delay-property/
We didn't have a choice, in the end we use TickFrequency because we need to save the changes in the slider to db, and we don't want it to happen for every 0.001..
thanks anyway..
I am creating a custom ComboBox control with Google Search results implementation but I am running into some problem using the arrow keys.
Details of the problem
Please watch this video as I demonstrate the problem with UP and DOWN arrow keys and take a look on the output panel as well.
http://screencast.com/t/DFkmlDKR
In the video, I tried searching for "a" (just a test) and returns the Google search results that starts with "a" then I am pressing the DOWN and UP arrow keys. In the output panel, it shows the highlighted item as I press these keys.
Then I typed the next letter "l". Now If you noticed on the output panel, the selection is now "null" but I'm still pressing the UP & DOWN arrow keys.
And when you hover on the item again using the mouse pointer, it will start working again.
I am stuck with this problem for days and haven't figured out the solution.
I have uploaded the test version of this control so you can play with it as well. Here it is GoogleSuggestionComboBox
My goal here is to make the UP & DOWN arrow keys work all the time.
In this section of the code
I tried adding
SelectedIndex = 0;
after the ForEach statement so everytime the collection is repopulated with the new results, it will select the first result. Unfortunately, it did not work.
You can download the test code so you can play and test the problem. http://sdrv.ms/1eWV3Bc and here's the code for the ComboBox as well.
using GoogleSuggestionComboBox.Model;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace GoogleSuggestionComboBox
{
public class ComboBoxExC : ComboBox
{
GoogleSuggest google;
TextBox textbox;
string _text = string.Empty;
string _last_text = string.Empty;
public ComboBoxExC()
{
if (DesignerProperties.GetIsInDesignMode(this))
{
}
else
{
this.Loaded += ComboBoxExC_Loaded;
google = new GoogleSuggest();
google.OnGoogleSuggestAvailable += google_OnGoogleSuggestAvailable;
// since we have OnSelectionChanged "disabled"
// we need a way to know if the item in ComboBox is selected using mouse
EventManager.RegisterClassHandler(typeof(ComboBoxItem), ComboBoxItem.MouseDownEvent, new MouseButtonEventHandler(OnItemMouseDown));
}
}
void ComboBoxExC_Loaded(object sender, RoutedEventArgs e)
{
this.textbox = (TextBox)Template.FindName("PART_EditableTextBox", this);
}
private void OnItemMouseDown(object sender, MouseButtonEventArgs e)
{
var comboBoxItem = sender as ComboBoxItem;
if (comboBoxItem != null && comboBoxItem.IsHighlighted)
{
Model_SuggestedQueries m = (Model_SuggestedQueries)comboBoxItem.Content;
Go(m.Query);
}
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
// don't do anything so the .Text value won't change.
//base.OnSelectionChanged(e);
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
this.IsDropDownOpen = true;
base.OnPreviewKeyDown(e);
d("key: " + e.Key.ToString());
if (this.SelectedItem != null)
{
Model_SuggestedQueries m = (Model_SuggestedQueries)this.SelectedItem;
d("selected: " + m.Query);
}
else
{
d("null");
}
}
protected override void OnPreviewKeyUp(KeyEventArgs e)
{
base.OnPreviewKeyUp(e);
if (e.Key == Key.Enter)
{
if (this.SelectedItem == null)
{
Go(this.Text);
}
else
{
Model_SuggestedQueries m = (Model_SuggestedQueries)this.SelectedItem;
Go(m.Query);
}
}
else
{
if (this.Text != this._last_text)
{
google.LookForSuggestion(this.Text);
this._last_text = this.Text;
}
}
}
void google_OnGoogleSuggestAvailable(object sender, List<Model.Model_SuggestedQueries> suggestions)
{
this.Items.Clear();
suggestions.ForEach((a) =>
{
this.Items.Add(a);
});
}
void d(object a)
{
Debug.WriteLine(">>> " + a);
}
void Go(string query)
{
Process.Start("https://www.google.com.ph/search?q=" + query);
// clear suggestions
this.Items.Clear();
}
}
}
MainWindow.xaml
<Window x:Class="GoogleSuggestionComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:GoogleSuggestionComboBox"
Title="MainWindow" Height="133" Width="261" WindowStartupLocation="CenterScreen"
>
<Grid>
<StackPanel Margin="10">
<TextBlock Text="Search" />
<l:ComboBoxExC
IsEditable="True"
IsTextSearchEnabled="False"
TextSearch.TextPath="Query"
>
<l:ComboBoxExC.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Query}" />
</DataTemplate>
</l:ComboBoxExC.ItemTemplate>
</l:ComboBoxExC>
</StackPanel>
</Grid>
</Window>
Thank you,
Jayson
This is a pretty old question but I ran into about the same problem. The main issue for me was after selecting an item or clearing selection (setting it to -1) I could not go through the items with the arrow keys anymore until I hovered over them with my mouse.
After some days of struggling I found a fix for this:
KeyboardNavigation.SetDirectionalNavigation( this, KeyboardNavigationMode.Cycle );
this is the combobox, and this line is in the constructor of a sub classed combobox.
I don't know the exact issue, but here is a suggestion:
Don't subclass the ComboBox - you don't need to.
Just drop one on the MainForm, and then work with it from the code behind - it is much easier to work with it like that. You only need to subclass if you need to change the way a combo-box works which is not typical.
I tried downloading the test project, but it is not a zip file - it is a .7z which I could not open.
Also, you main form is a Window. That may not be an issue, but try creating a new project and just working with the Main form the project gives you. There is some wiring up that is done in the App that calls the MainForm that will catch exceptions etc, and that may give you more data as to what the issue is.
Greg
I am porting my program from WinForms to WPF and have ran into some issues with the drag and drop. It should allow for dragging from a TreeView (it is like a file explorer) to a textbox which opens the file. However, the WPF version acts like a copy-and-paste of the TreeViewItem's header text automatically. I think I just have something mixed up? Possibly the DataObject stuff.
The fully functional, relevant WinForms code:
private void treeView1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
TreeNode node = treeView1.GetNodeAt(e.Location);
if (node != null) treeView1.DoDragDrop(node, DragDropEffects.Move);
}
textbox[i].DragDrop += (o, ee) =>
{
if (ee.Data.GetDataPresent(typeof(TreeNode)))
{
TreeNode node = (TreeNode)ee.Data.GetData(typeof(TreeNode));
((Textbox)o).Text = File.ReadAllLines(pathRoot + node.Parent.FullPath);
...
The WPF code that should do the same thing:
private void TreeView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem item = e.Source as TreeViewItem;
if (item != null)
{
DataObject dataObject = new DataObject();
dataObject.SetData(DataFormats.StringFormat, GetFullPath(item));
DragDrop.DoDragDrop(item, dataObject, DragDropEffects.Move);
}
}
//textbox[i].PreviewDrop += textbox_Drop;
private void textbox_Drop(object sender, DragEventArgs e)
{
TreeViewItem node = (TreeViewItem)e.Data.GetData(typeof(TreeViewItem)); //null?
((Textbox)sender).Text = "";
//this is being executed BUT then the node's header text is being pasted
//also, how do I access the DataObject I passed?
}
Problem: In my WPF version, I am setting the textbox's text to empty (as a test), which occurs, but afterwards the TreeViewItem's header text is being pasted which is not what I want.
Questions: What is the correct way to port this WinForms code to WPF? Why is the text being pasted in the WPF version? How do I prevent that? Am I using the correct events? How do I access the DataObject in textbox_Drop so that I can open the file like I did in the WinForms version? Why is TreeViewItem node always null in the WPF version?
Ah, what the heck, I'll expand my comment to an answer:
The link to read, as mentioned, is this:
http://msdn.microsoft.com/en-us/library/hh144798.aspx
Short story, the TextBox-derived controls already implement most of the "guts" for basic drag/drop operations, and it is recommended that you extend that rather than provide explicit DragEnter/DragOver/Drop handlers.
Assuming a tree "data" structure like:
public class TreeThing
{
public string Description { get; set; }
public string Path { get; set; }
}
The handlers might look something like this:
this.tb.AddHandler(UIElement.DragOverEvent, new DragEventHandler((sender, e) =>
{
e.Effects = !e.Data.GetDataPresent("treeThing") ?
DragDropEffects.None :
DragDropEffects.Copy;
}), true);
this.tb.AddHandler(UIElement.DropEvent, new DragEventHandler((sender, e) =>
{
if (e.Data.GetDataPresent("treeThing"))
{
var item = e.Data.GetData("treeThing") as TreeThing;
if (item != null)
{
tb.Text = item.Path;
// TODO: Actually open up the file here
}
}
}), true);
And just for giggles, here's a quick-and-dirty test app that is pure showboating in it's use of the Reactive Extensions (Rx) for the drag start stuff:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView x:Name="tree" Grid.Column="0" ItemsSource="{Binding TreeStuff}" DisplayMemberPath="Description"/>
<TextBox x:Name="tb" Grid.Column="1" AllowDrop="True" Text="Drop here" Height="30"/>
</Grid>
</Window>
Nasty code-behind (too lazy to MVVM this):
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
TreeStuff = new ObservableCollection<TreeThing>()
{
new TreeThing() { Description="file 1", Path = #"c:\temp\test.txt" },
new TreeThing() { Description="file 2", Path = #"c:\temp\test2.txt" },
new TreeThing() { Description="file 3", Path = #"c:\temp\test3.txt" },
};
var dragStart =
from mouseDown in
Observable.FromEventPattern<MouseButtonEventHandler, MouseEventArgs>(
h => tree.PreviewMouseDown += h,
h => tree.PreviewMouseDown -= h)
let startPosition = mouseDown.EventArgs.GetPosition(null)
from mouseMove in
Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => tree.MouseMove += h,
h => tree.MouseMove -= h)
let mousePosition = mouseMove.EventArgs.GetPosition(null)
let dragDiff = startPosition - mousePosition
where mouseMove.EventArgs.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(dragDiff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(dragDiff.Y) > SystemParameters.MinimumVerticalDragDistance)
select mouseMove;
dragStart.ObserveOnDispatcher().Subscribe(start =>
{
var nodeSource = this.FindAncestor<TreeViewItem>(
(DependencyObject)start.EventArgs.OriginalSource);
var source = start.Sender as TreeView;
if (nodeSource == null || source == null)
{
return;
}
var data = (TreeThing)source
.ItemContainerGenerator
.ItemFromContainer(nodeSource);
DragDrop.DoDragDrop(nodeSource, new DataObject("treeThing", data), DragDropEffects.All);
});
this.tb.AddHandler(UIElement.DragOverEvent, new DragEventHandler((sender, e) =>
{
e.Effects = !e.Data.GetDataPresent("treeThing") ?
DragDropEffects.None :
DragDropEffects.Copy;
}), true);
this.tb.AddHandler(UIElement.DropEvent, new DragEventHandler((sender, e) =>
{
if (e.Data.GetDataPresent("treeThing"))
{
var item = e.Data.GetData("treeThing") as TreeThing;
if (item != null)
{
tb.Text = item.Path;
// TODO: Actually open up the file here
}
}
}), true);
this.DataContext = this;
}
private T FindAncestor<T>(DependencyObject current)
where T:DependencyObject
{
do
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
}
while (current != null);
return null;
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<TreeThing> TreeStuff { get; set; }
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class TreeThing
{
public string Description { get; set; }
public string Path { get; set; }
}
}
You've got more than one problem, enough to make this difficult. First issue is that you got the drag object wrong, you are dragging a string but still checking for a TreeViewItem. Just use the same approach as you used in Winforms, dragging the node. Second problem is that TextBox already implements D+D support and that gets in the way of your code. And the reason you saw the text show up after the drop.
Let's tackle the start of the drag first. You'll need to do a bit of extra work since the way you started the drag interferes with the normal usage of the TreeView, it gets very hard to select a node. Only start the drag when the mouse was moved far enough:
private Point MouseDownPos;
private void treeView1_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
MouseDownPos = e.GetPosition(treeView1);
}
private void treeView1_PreviewMouseMove(object sender, MouseEventArgs e) {
if (e.LeftButton == MouseButtonState.Released) return;
var pos = e.GetPosition(treeView1);
if (Math.Abs(pos.X - MouseDownPos.X) >= SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(pos.Y - MouseDownPos.Y) >= SystemParameters.MinimumVerticalDragDistance) {
TreeViewItem item = e.Source as TreeViewItem;
if (item != null) DragDrop.DoDragDrop(item, item, DragDropEffects.Copy);
}
}
Now the drop, you will need to implement the DragEnter, DragOver and Drop event handlers to avoid the default D+D support built into TextBox from getting in the way. Setting the e.Handled property to true is necessary:
private void textBox1_PreviewDragEnter(object sender, DragEventArgs e) {
if (e.Data.GetDataPresent(typeof(TreeViewItem))) e.Effects = e.AllowedEffects;
e.Handled = true;
}
private void textBox1_PreviewDrop(object sender, DragEventArgs e) {
var item = (TreeViewItem)e.Data.GetData(typeof(TreeViewItem));
textBox1.Text = item.Header.ToString(); // Replace this with your own code
e.Handled = true;
}
private void textBox1_PreviewDragOver(object sender, DragEventArgs e) {
e.Handled = true;
}
Problem: In my WPF version, I am setting the textbox's text to empty (as a test), which occurs, but afterwards the TreeViewItem's header text is being pasted which is not what I want.
I think a parent UI element is handling (and therefore overriding) the Drop event so you're not getting the results you expect. As a matter of fact, when trying to recreate your issue, I couldn't even get my TextBox.Drop event to fire. However, using the TextBox's PreviewDrop event, I was able to get what (I think) is your expected result. Try this:
private void textBox1_PreviewDrop(object sender, DragEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null)
{
// If the DataObject contains string data, extract it.
if (e.Data.GetDataPresent(DataFormats.StringFormat))
{
string fileName = e.Data.GetData(DataFormats.StringFormat) as string;
using (StreamReader s = File.OpenText(fileName))
{
((TextBox)sender).Text = s.ReadToEnd();
}
}
}
e.Handled = true; //be sure to set this to true
}
I think that code snippet should answer most of the questions you posed except for this one:
Why is TreeViewItem node always null in the WPF version?
The DataObject you are passing in the DragDrop event does not support passing a TreeViewItem. In your code (and mine) we specify that the data format will be DataFormats.StringFormat which cannot be cast to a TreeViewItem.
GetFullPath seems to be outputting a wrong value. What you want to drag/drop is the Header and you can get it directly from item. Also bear in mind that the method below is associated with the MouseMove Event of the TreeView.
private void TreeView_MouseMove(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed) return;
TreeViewItem item = e.Source as TreeViewItem;
if (item != null)
{
DataObject dataObject = new DataObject();
dataObject.SetData(DataFormats.StringFormat, item.Header);
DragDrop.DoDragDrop(item, dataObject, DragDropEffects.Move);
}
}
I did create the drop part based on text rather than on the TreeViewItem (e.Data.GetData(typeof(string)).ToString()) but the most surprising thing is that it isn't even required. If you open a new C# WPF project, put a TreeView and a TextBox on it (, update the XAML part) and copy the code above, you can drop text from the TreeView into the TextBox without doing anything else!! The text is copied into the TextBox without accounting for the Drop handling.
Am I using the correct events?:
I think you are using the correct events, but I think you have several problems in your code.
I assume you have set the DataContext of your treeview to the real items and you use binding.
How do I access the DataObject in textbox_Drop ? -->
For getting the DataObject you have to get the real item by recursion (other solutions possible)
DependencyObject k = VisualTreeHelper.HitTest(tv_treeView, DagEventArgs.GetPosition(lv_treeView)).VisualHit;
while (k != null)
{
if (k is TreeViewItem)
{
TreeViewItem treeNode = k as TreeViewItem;
// Check if the context is your desired type
if (treeNode.DataContext is YourType)
{
// save the item
targetTreeViewItem = treeNode;
return;
}
}
else if (k == tv_treeview)
{
Console.WriteLine("Found treeview instance");
return;
}
// Get the parent item if no item from YourType was found
k = VisualTreeHelper.GetParent(k);
}
Why is the text being pasted in the WPF version? -->
The Header is displayed because (I assume) it is like the tostring method on your items. If for a complex item the binding is not specified, the ToString Method is executed.
Try not to set the Text directly in the handler of the drop event. Set the data context to your item (to the item you found in 1. point) and then specify the binding path via XAML. (for displaying)
I am developing a WPF for touch enabled device. I am facing a strange problem. My XAML structure is
<ScrollViewer>
<StackPanel orientation="Horizontal">
<!-- Control goes here -->
</StackPanel>
<ScrollViewer>
Now
To enable scrolling on touch I have to set PannigMode to HorizontalOnly
To receive manipulation events I have to set PannigMode to None
Problem is I have to have these two functionality simultaneously.
Is there any work around so that the scrollviewer is scrolled on touch and also the manipulationcompleted event fires.
Please help.
I had the same issue. You have at least two options
use MS Surface Toolkit
fix ScrollViewer
I've chosen the second one. Simply create a custom control, inherit from ScrollViewer, in Generic.xaml you only need to put a <ContentPresenter />, nothing more.
The real job is in the code behind, but also not so complecated. I had to check whether the user touched a button or just wanted to scroll. The trick is to check what is on the touch points and turn on / off the panning mode.
And here is the code:
namespace Demo.Controls
{
public class ScrollViewerWithTouch : ScrollViewer
{
private PanningMode panningMode;
private bool panningModeSet;
static ScrollViewerWithTouch()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollViewerWithTouch), new FrameworkPropertyMetadata(typeof(ScrollViewerWithTouch)));
}
protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
{
base.OnManipulationCompleted(e);
// set it back
this.PanningMode = this.panningMode;
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
// figure out what has the user touched
var result = VisualTreeHelper.HitTest(this, e.ManipulationOrigin);
if (result != null && result.VisualHit != null)
{
var hasButtonParent = this.HasButtonParent(result.VisualHit);
// if user touched a button then turn off panning mode, let style bubble down, in other case let it scroll
this.PanningMode = hasButtonParent ? PanningMode.None : this.panningMode;
}
base.OnManipulationStarted(e);
}
protected override void OnTouchDown(TouchEventArgs e)
{
// store panning mode or set it back to it's original state. OnManipulationCompleted does not do it every time, so we need to set it once more.
if (this.panningModeSet == false)
{
this.panningMode = this.PanningMode;
this.panningModeSet = true;
}
else
{
this.PanningMode = this.panningMode;
}
base.OnTouchDown(e);
}
private bool HasButtonParent(DependencyObject obj)
{
var parent = VisualTreeHelper.GetParent(obj);
if ((parent != null) && (parent is ButtonBase) == false)
{
return HasButtonParent(parent);
}
return parent != null;
}
}
}
In my application I have numerical (double or int) ViewModel properties that are bound to TextBoxes. The ViewModel implements IDataErrorInfo to check if the values entered fall within acceptable ranges for the 'business logic' (e.g. height can't be a negative value). I have a number of TextBoxes per page and have a button (think 'next' in a wizard) thats enabled property is bound to a ViewModel boolean that specifies whether there are any errors on the page as a whole. The enable/disable state of the button is properly updated with valid/invalid values according to the IDataErrorInfo rules I've written.
However, there is no way to let my viewmodel know when an exception has been thrown because an input value does not convert (i.e. "12bd39" is not a valid double) and as a result in the case of conversion exceptions my 'next' button will remain enabled despite bad input. The GUI however properly reflects the error with an adorner because of my binding:
<TextBox Text="{Binding Temperature, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
How can I let the view know that a 'ValidatesOnExceptions' style error has occured. Josh Smith's take here seems to rely on making every ViewModel property a string and rolling your own exception checking which seems like a lot of additional work. I additionally began looking into Karl Shifflett's implementation here, but I cannot seem to capture the routed event I would expect when putting this code into the view's codebehind file:
public ViewClass()
{
this.InitializeComponent();
this.AddHandler(System.Windows.Controls.Validation.ErrorEvent, new RoutedEventHandler(ValidationErrorHandler));
}
private void ValidationErrorHandler(object sender, RoutedEventArgs e)
{
var blah = e as System.Windows.Controls.ValidationErrorEventArgs;
if (blah.Action == ValidationErrorEventAction.Added)
{
}
else if (blah.Action == ValidationErrorEventAction.Removed)
{
}
}
Silverlight appears to have an event that you can subscribe too, but I cannot find the exact equivalent in WPF (3.5). Any help is appreciated!
I have a base class for the View that subscribes to Validation.ErrorEvent routed event
public class MVVMViewBase : UserControl
{
private RoutedEventHandler _errorEventRoutedEventHandler;
public MVVMViewBase()
{
Loaded += (s, e) =>
{
_errorEventRoutedEventHandler = new RoutedEventHandler(ExceptionValidationErrorHandler);
AddHandler(Validation.ErrorEvent, _errorEventRoutedEventHandler);
};
Unloaded += (s, e) =>
{
if (_errorEventRoutedEventHandler != null)
{
RemoveHandler(Validation.ErrorEvent, _errorEventRoutedEventHandler);
_errorEventRoutedEventHandler = null;
}
};
}
private void ExceptionValidationErrorHandler(object sender, RoutedEventArgs e)
{
ValidationErrorEventArgs args = (ValidationErrorEventArgs) e;
if (!(args.Error.RuleInError is IUiValidation)) return;
DataErrorInfoViewModelBase viewModelBase = DataContext as DataErrorInfoViewModelBase;
if(viewModelBase == null) return;
BindingExpression bindingExpression = (BindingExpression) args.Error.BindingInError;
string dataItemName = bindingExpression.DataItem.ToString();
string propertyName = bindingExpression.ParentBinding.Path.Path;
e.Handled = true;
if(args.Action == ValidationErrorEventAction.Removed)
{
viewModelBase.RemoveUIValidationError(new UiValidationError(dataItemName, propertyName, null));
return;
}
string validationErrorText = string.Empty;
foreach(ValidationError validationError in Validation.GetErrors((DependencyObject) args.OriginalSource))
{
if (validationError.RuleInError is IUiValidation)
{
validationErrorText = validationError.ErrorContent.ToString();
}
}
viewModelBase.AddUIValidationError(new UiValidationError(dataItemName, propertyName, validationErrorText));
}
}
and a base class for the ViewModel = DataErrorInfoViewModelBase that is informed by
AddUIValidationError and RemoveUIValidationError
Also all my ValidationRule classes implement IUiValidation which is used just to mark the class as taking part of the UI errors propagation(no members). (you can use an attribute for the same purpose).