How to handle purely view-related commands? - c#

There is my WPF window in which I placed an ordinary textbox which I would liked to be focused when Ctrl+F is pressed.
As I would like to keep it MVVM-like as much as possible, I use InputBindings on the window to bind that input event to a Command provided in the ViewModel (is that already breaking MVVM pattern because the whole action is only meant to be part of the view? I guess not, as the Command is an object to bind to).
How can the ViewModel communicate with the view to focus the textbox? I read that this already breaks the MVVM pattern, but sometimes simply is necessary as otherwise impossible. However, setting the focus in the ViewModel itself would be totally breaking the MVVM pattern.
I orginally intended to bind the current focused control in the window to a property of the ViewModel but it is quite difficult to even determine the currently focused element in WPF (that always makes me question if it really is the right way to do so).

In cases like this there's just no way to not 'break' pure MVVM. Then again, I'd hardly call it breaking anything. I don't think any decently sized MVVM app out there is 'pure'. So, just stop caring too much about breaking whatever pattern you use and implement a solution instead.
There are at least two ways here:
simply do everything in code behind in the View: check if the key is pressed, if so, set focus. It won't get any simpler than that and you could argue the VM has nothing to do with something that's really all View related
else there is obviously going to have to be some communication between VM and View. And this makes everything more complicated: suppose you use the InputBinding, your command can set a boolean property and then the View can bind to it in turn to set focus. That binding can be done like in Sheridan's answer with an attached property.

Generally, when we want to use any UI event while adhering to the MVVM methodology, we create an Attached Property. As I just answered this very same question yesterday, I would advise you to take a look at the how to set focus to a wpf control using mvvm post here on StackOverflow for a full working code example.
The only difference from that question to yours is that you want to focus the element on a key press... I'm going to assume that you know how to do that part, but if you can't, just let me know and I'll give you an example of that too.

when using mvvm and further when you define a viewmodel with:
a viewmodel should not know/reference the view
then you cant set focus through the viewmodel.
but what i do in mvvm is the following in the viewmodel:
set the focus to the element which is bind to the viewmodel property
for this i create a behavior which simply walk through all control in the visual tree and look for the binding expressions path. and if i find a path expression then simply focus the uielement.
EDIT:
xaml usage
<UserControl>
<i:Interaction.Behaviors>
<Behaviors:OnLoadedSetFocusToBindingBehavior BindingName="MyFirstPropertyIWantToFocus" SetFocusToBindingPath="{Binding Path=FocusToBindingPath, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</UserControl>
viemodel in any method
this.FocusToBindingPath = "MyPropertyIWantToFocus";
behavior
public class SetFocusToBindingBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty SetFocusToBindingPathProperty =
DependencyProperty.Register("SetFocusToBindingPath", typeof(string), typeof(SetFocusToBindingBehavior ), new FrameworkPropertyMetadata(SetFocusToBindingPathPropertyChanged));
public string SetFocusToBindingPath
{
get { return (string)GetValue(SetFocusToBindingPathProperty); }
set { SetValue(SetFocusToBindingPathProperty, value); }
}
private static void SetFocusToBindingPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as SetFocusToBindingBehavior;
var bindingpath = (e.NewValue as string) ?? string.Empty;
if (behavior == null || string.IsNullOrWhiteSpace(bindingpath))
return;
behavior.SetFocusTo(behavior.AssociatedObject, bindingpath);
//wenn alles vorbei ist dann binding path zurücksetzen auf string.empty,
//ansonsten springt PropertyChangedCallback nicht mehr an wenn wieder zum gleichen Propertyname der Focus gesetzt werden soll
behavior.SetFocusToBindingPath = string.Empty;
}
private void SetFocusTo(DependencyObject obj, string bindingpath)
{
if (string.IsNullOrWhiteSpace(bindingpath))
return;
var ctrl = CheckForBinding(obj, bindingpath);
if (ctrl == null || !(ctrl is IInputElement))
return;
var iie = (IInputElement) ctrl;
ctrl.Dispatcher.BeginInvoke((Action)(() =>
{
if (!iie.Focus())
{
//zb. bei IsEditable=true Comboboxen funzt .Focus() nicht, daher Keyboard.Focus probieren
Keyboard.Focus(iie);
if (!iie.IsKeyboardFocusWithin)
{
Debug.WriteLine("Focus konnte nicht auf Bindingpath: " + bindingpath + " gesetzt werden.");
var tNext = new TraversalRequest(FocusNavigationDirection.Next);
var uie = iie as UIElement;
if (uie != null)
{
uie.MoveFocus(tNext);
}
}
}
}), DispatcherPriority.Background);
}
public string BindingName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectLoaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObjectLoaded;
}
private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
SetFocusTo(AssociatedObject, this.BindingName);
}
private DependencyObject CheckForBinding(DependencyObject obj, string bindingpath)
{
var properties = TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) });
if (obj is IInputElement && ((IInputElement) obj).Focusable)
{
foreach (PropertyDescriptor property in properties)
{
var prop = DependencyPropertyDescriptor.FromProperty(property);
if (prop == null) continue;
var ex = BindingOperations.GetBindingExpression(obj, prop.DependencyProperty);
if (ex == null) continue;
if (ex.ParentBinding.Path.Path == bindingpath)
return obj;
}
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var result = CheckForBinding(VisualTreeHelper.GetChild(obj, i),bindingpath);
if (result != null)
return result;
}
return null;
}
}

(is that already breaking MVVM pattern because the whole action is
only meant to be part of the view? I guess not, as the Command is an
object to bind to)
The Command system in WPF was actually not designed around data-binding, but the UI -- using RoutedCommands, a single command would have different implementations based on the physical position in the UI structure of the element that called the command.
Commanding Overview
Your flow would be:
Ctrl+F is pressed
command event is raised and bubbles up
the event reaches the window, which has a CommandBinding to the command
event handler on the window focuses the text box
If the current element is inside a container that wants to handle the command differently, it will stop there before it reaches the window.
This is probably closer to what you want. It may make sense to involve the view model if there is some concept of an "active property" like in blindmeis's answer, but otherwise I think you would just end up with a redundant / circular flow of information e.g. key pressed -> view informs viewmodel of keypress -> viewmodel responds by informing view of keypress.

After a few days of getting a better grip on all of this, considering and evaluating all options, I finally found a way to work it out. I add a command binding in my window markup:
<Window.InputBindings>
<KeyBinding Command="{Binding Focus}" CommandParameter="{Binding ElementName=SearchBox}" Gesture="CTRL+F" />
</Window.InputBindings>
The command in my ViewModel (I cut the class down to what matters in this case):
class Overview : Base
{
public Command.FocusUIElement Focus
{
get;
private set;
}
public Overview( )
{
this.Focus = new Command.FocusUIElement();
}
}
And finally the command itself:
class FocusUIElement : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute ( object parameter )
{
return true;
}
public void Execute ( object parameter )
{
System.Windows.UIElement UIElement = ( System.Windows.UIElement ) parameter;
UIElement.Focus();
}
}
This might not be straigt MVVM - but stijn's answer has a good point:
So, just stop caring too much about breaking whatever pattern you use
and implement a solution instead.
Normally I take care of keeping stuff organised by conventions, especially when I am still new to something, but I do not see anything wrong regarding this.

Related

Copying a TabItem with an MVVM structure

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);

C# Dynamic form (reflection) - linking controls

Sorry for the poor quality of the title. I couldn't think of a better way to phrase this.
For a project I'm currently working on with a few friends, I got myself in the situation where I have created a dynamic form (with reflection) which I now want to validate.
Example (ignore the black box, it contains old form elements which are now irrelevant and i didn't want to confuse you guys):
As you may have guessed already, it is an application for creating a mysql database.
Which is where I get to my problem(s). I want to disable checkboxes if others are checked.
For example: If I check "PrimaryKey" I want to disable the checkbox "Null".
Changing from unsigned to signed changes the numericupdown minimum and maximum etc.
But with reflection and all, I find it difficult to know exactly which checkbox to disable.
I was hoping you guys would have some suggestions.
I have been thinking about this for a while and a few thoughts have come to mind. Maybe these are better solutions than the current one.
Thought 1: I create UserControls for every datatype. Pro's: no problems with reflection and easy identifying of every control in the UserControl for validation. Con's: Copy-Pasting, Lots of UserControls, with a lot of the same controls.
Thought 2: Doing something with the description tags for every property of the classes. Creating rules in the description that allow me to link the checkboxes together. Here I'll only have to copy the rules to every class property and then it should be ok.
I had been thinking of other solutions but I failed to remember them.
I hope you guys can give me a few good pointers/suggestions.
[Edit]
Maybe my code can explain a bit more.
My code:
PropertyInfo[] properties = DataTypes.DataTypes.GetTypeFromString(modelElement.DataType.ToString()).GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
if (prop.Name != "Label" && prop.Name != "Project" && prop.Name != "Panel")
{
var value = prop.GetValue(modelElement.DataType, null);
if (value != null)
{
tableLayoutPanel1.Controls.Add(new Label { Text = prop.Name, Anchor = AnchorStyles.Left, AutoSize = true });
switch (value.GetType().ToString())
{
case "System.Int32":
NumericUpDown numericUpDown = new NumericUpDown();
numericUpDown.Text = value.ToString();
numericUpDown.Dock = DockStyle.None;
tableLayoutPanel1.Controls.Add(numericUpDown);
break;
case "System.Boolean":
CheckBox checkBox = new CheckBox();
checkBox.Dock = DockStyle.None;
// checkbox will become huge if not for these changes
checkBox.AutoSize = false;
checkBox.Size = new Size(16, 16);
if (value.Equals(true))
{
checkBox.CheckState = CheckState.Checked;
}
tableLayoutPanel1.Controls.Add(checkBox);
break;
default:
MessageBox.Show(#"The following type has not been implemented yet: " + value.GetType());
break;
}
}
}
}
Here is a mockup from my comments:
// The ViewModel is responsible for handling the actual visual layout of the form.
public class ViewModel {
// Fire this when your ViewModel changes
public event EventHandler WindowUpdated;
public Boolean IsIsNullCheckBoxVisible { get; private set; }
// This method would contain the actual logic for handling window changes.
public void CalculateFormLayout() {
Boolean someLogic = true;
// If the logic is true, set the isNullCheckbox to true
if (someLogic) {
IsIsNullCheckBoxVisible = true;
}
// Inform the UI to update
UpdateVisual();
}
// This fires the 'WindowUpdated' event.
public void UpdateVisual() {
if (WindowUpdated != null) {
WindowUpdated(this, new EventArgs());
}
}
}
public class TheUI : Form {
// Attach to the viewModel;
ViewModel myViewModel = new ViewModel();
CheckBox isNullCheckBox = new CheckBox();
public TheUI() {
this.myViewModel.WindowUpdated += myViewModel_WindowUpdated;
}
void myViewModel_WindowUpdated(object sender, EventArgs e) {
// Update the view here.
// Notie that all we do in the UI is to update the visual based on the
// results from the ViewModel;
this.isNullCheckBox.Visible = myViewModel.IsIsNullCheckBoxVisible;
}
}
The basic idea here is that you ensure that the UI does as little as possible. It's role should just be to update. Update what? That's for the ViewModel class to decide. We perform all of the updating logic in the ViewModel class, and then when the updating computations are done, we call the UpdateVisual() event, which tells the UI that it needs to represent itself. When the WindowUpdated Event occurs, the UI just responds by displaying the configuration set up by the ViewModel.
This may seem like a lot of work to set up initially, but once in place it will save you tons and tons of time down the road. Let me know if you have any questions.
Try relating the event of one checkbox to disable the other; something like this:
private void primaryKeyBox_AfterCheck(object sender, EventArgs e)
{
nullBox.Enabled = false;
}
This is a very simple example and would have to be changed a bit, but for what I think you're asking it should work. You would also have to add to an event for the boxes being unchecked. You would also need logic to only get data from certain checkboxes based on the ones that are and are not checked.
For all the other things, such as changing the numbers based on the dropdown, change them based on events as well.
For WinForms I would use data binding.
Create an object and implement INotifyPropertyChanged and work with that object.
Then, If you have an object instance aObj:
To bind the last name property to a textbox on the form do this:
Private WithEvents txtLastNameBinding As Binding
txtLastNameBinding = New Binding("Text", aObj, "LastName", True, DataSourceUpdateMode.OnValidation, "")
txtLastName.DataBindings.Add(txtLastNameBinding)
Take a look here for more info.
INotifyPropertyChanged

TextBox does not always update

I have the following TextBox:
<TextBox Text="{Binding SearchString,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
Bound to the following property:
private string _searchString;
public string SearchString
{
get
{
return _searchString;
}
set
{
value = Regex.Replace(value, "[^0-9]", string.Empty);
_searchString = value;
DoNotifyPropertyChanged("SearchString");
}
}
The class inherits from a base class that implements INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void DoNotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
All I want is a quick and dirty way to disallow non-numerical characters for an integer-only text box (I know it's not complete, just for demonstration). I don't want a mere notification that there is illegal text or anything, I want to discard right away all characters on input that are disallowed.
However, the TextBox is behaving weirdly. I can still enter any text I want, it will display as entered, e.g. "1aaa". Even though the property has been properly cleaned to "1" in this example, the Textbox still shows "1aaa". Only when I enter an actual digit that would cause _searchString to change does it also update the displayed text, for example when I have "1aaa2" it will correctly update to "12". What's the matter here?
This sounds like view-specific logic, so I see no reason not to use code-behind the view to control it. Personally I would implement this kind of behavior with a PreviewKeyDown on the TextBox that discards any non-numeric characters.
It probably wouldn't hurt to have something generic that you could reuse, such as a custom NumbersOnlyTextBox control, or an AttachedProperty you could attach to your TextBox to specify that it only allows numbers.
In fact, I remember creating an attached property that allows you to specify a regex for a textbox, and it will limit character entry to just that regex. I haven't used it in a while, so you'll probably want to test it or possible update it, but here's the code.
// When set to a Regex, the TextBox will only accept characters that match the RegEx
#region AllowedCharactersRegex Property
/// <summary>
/// Lets you enter a RegexPattern of what characters are allowed as input in a TextBox
/// </summary>
public static readonly DependencyProperty AllowedCharactersRegexProperty =
DependencyProperty.RegisterAttached("AllowedCharactersRegex",
typeof(string), typeof(TextBoxProperties),
new UIPropertyMetadata(null, AllowedCharactersRegexChanged));
// Get
public static string GetAllowedCharactersRegex(DependencyObject obj)
{
return (string)obj.GetValue(AllowedCharactersRegexProperty);
}
// Set
public static void SetAllowedCharactersRegex(DependencyObject obj, string value)
{
obj.SetValue(AllowedCharactersRegexProperty, value);
}
// Events
public static void AllowedCharactersRegexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var tb = obj as TextBox;
if (tb != null)
{
if (e.NewValue != null)
{
tb.PreviewTextInput += Textbox_PreviewTextChanged;
DataObject.AddPastingHandler(tb, TextBox_OnPaste);
}
else
{
tb.PreviewTextInput -= Textbox_PreviewTextChanged;
DataObject.RemovePastingHandler(tb, TextBox_OnPaste);
}
}
}
public static void TextBox_OnPaste(object sender, DataObjectPastingEventArgs e)
{
var tb = sender as TextBox;
bool isText = e.SourceDataObject.GetDataPresent(DataFormats.Text, true);
if (!isText) return;
var newText = e.SourceDataObject.GetData(DataFormats.Text) as string;
string re = GetAllowedCharactersRegex(tb);
re = "[^" + re + "]";
if (Regex.IsMatch(newText.Trim(), re, RegexOptions.IgnoreCase))
{
e.CancelCommand();
}
}
public static void Textbox_PreviewTextChanged(object sender, TextCompositionEventArgs e)
{
var tb = sender as TextBox;
if (tb != null)
{
string re = GetAllowedCharactersRegex(tb);
re = "[^" + re + "]";
if (Regex.IsMatch(e.Text, re, RegexOptions.IgnoreCase))
{
e.Handled = true;
}
}
}
#endregion // AllowedCharactersRegex Property
It would be used like this:
<TextBox Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
local:TextBoxHelpers.AllowedCharactersRegex="[0-9]" />
But as to why it won't update the UI. The UI knows the value hasn't actually changed, so doesn't bother re-evaluating the binding when it receives the PropertyChange notification.
To get around that, you could try temporarily setting the value to something else before setting it to the regex value, and raising a PropertyChange notification so the UI re-evaluates the bindings, but honestly that isn't really an ideal solution.
private string _searchString;
public string SearchString
{
get
{
return _searchString;
}
set
{
value = Regex.Replace(value, "[^0-9]", string.Empty);
// If regex value is the same as the existing value,
// change value to null to force bindings to re-evaluate
if (_searchString == value)
{
_searchString = null;
DoNotifyPropertyChanged("SearchString");
}
_searchString = value;
DoNotifyPropertyChanged("SearchString");
}
}
I would guess that this has something to do with WPF's built-in infinite-loop prevention logic. As written, your logic would inform WPF that the property has changed each and every time that "Set" is called. When WPF is notified that the property has changed, it will update the control. When the control updates, it will (according to your binding) call the "Set" property again. ad infinitum. WPF was designed to detect these kinds of loops and prevent them to some degree - that's probably the trap you've ended up in.
I don't know exactly how this logic works, but I think Rachel's answer is going to get you the best results. In general, the ViewModel (what you are binding to) should be a reflection of the View, bad input and all. The ViewModel should be able to validate the input (not knowing where it came from or how it was entered) and prevent bad input from propogating to the Model (by trasitioning to an "error state", for example).
What you are trying to do is control what the user is inputting, which is probably better left to the View logic.
Why don't you look at
BindingOperations.GetBindingExpressionBase( _textBoxName, TextBox.TextProperty).UpdateTarget();
updating your XAML
<TextBox x:Name="_textBoxName" Text="{Binding SearchString,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
This forces an update from source to target, you're using a DependencyProperty and your control won't update because it knows the value while sending to the binding source.
MSDN: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingexpressionbase.updatetarget.aspx

custom binding .net forms

Is there any way to get custom binding behavior in .net win forms?
Example, I'm connecting my control to a BindingSource object and adding a binding like
this.slider.DataBindings.Add(new System.Windows.Forms.Binding("Enabled", this.bindingSourceModel, "FloatProperty > 0.5f", true));
There's no way the above will work but I like it to be enabled if dataSource.FloatProperty becomes greater than 0.5f.
Is there any way to do this?
I understand what you want to do, so I've slightly modified your situation for the sake of demonstration: the UI setup is obvious, there is a TrackBar and a Button, and the problem here is to bind the Enabled property of button to the boolean value of the expression trackBar.Value > 50.
The idea is to turn the main form into something like a ViewModel (as in MVVM). Observe that I am implementing INotifyPropertyChanged.
public partial class ManiacalBindingForm : Form, INotifyPropertyChanged {
public ManiacalBindingForm() {
InitializeComponent();
this.button.DataBindings.Add("Enabled", this, "ManiacalThreshold", true, DataSourceUpdateMode.OnPropertyChanged);
this.trackBar.ValueChanged += (s, e) => {
this.Text = string.Format("ManiacalBindingForm: {0}", this.trackBar.Value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ManiacalThreshold"));
};
}
public bool ManiacalThreshold {
get { return this.trackBar.Value > 50; }
}
public event PropertyChangedEventHandler PropertyChanged;
...
}
Now, this is my personal observation: While there is a non-trivial interpretation of your goal, your goal is a bit maniacal. You have to ponder why exactly you want to achieve this through data-binding. Binding is mostly aimed for automatic, bi-directional, sync'ing of property values. Doing this type of UI update via binding directly to the "model" is even more maniacal. But you got credit for being maniacal, though! ;-)

Informing ViewModel of ValidatesOnExceptions input errors

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).

Categories