Informing ViewModel of ValidatesOnExceptions input errors - c#

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

Related

C# - WPF - Prevent an update of a bound focused TextBox

I have a TextBox in a Windows Desktop WPF application bound to a property of a ViewModel. Now the user focuses the TextBox and starts entering a new value. During this time a background process gets a new Value for the same Property (e.g. because another user in a multi user environment enters a new value and an observer is detecting and propagating this change) and calls a PropertyChanged event for this Property. Now the value changes and the stuff the current user just entered is lost.
Is there a built in way to prevent the change while the TextBox is focused? Or do I have to build my own solution?
I think a custom control is needed to achieve the behavior you describe. By overriding a couple methods on the default WPF TextBox, we can keep the user input even if the View Model changes.
The OnTextChanged method will be called regardless of how our textbox is updated (both for keyboard events and View Model changes), but overriding the OnPreviewKeyDown method will separate out direct user-input. However, OnPreviewKeyDown does not provide easy access to the textbox value because it is also called for non-printable control characters (arrow keys, backspace, etc.)
Below, I made a WPF control that inherits from TextBox and overrides the OnPreviewKeyDown method to capture the exact time of the last user key-press. OnTextChanged checks the time and updates the text only if both events happen in quick succession.
If the last keyboard event was more than a few milliseconds ago, then the update probably did not happen from our user.
public class StickyTextBox : TextBox
{
private string _lastText = string.Empty;
private long _ticksAtLastKeyDown;
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
_ticksAtLastKeyDown = DateTime.Now.Ticks;
base.OnPreviewKeyDown(e);
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (!IsInitialized)
_lastText = Text;
if (IsFocused)
{
var elapsed = new TimeSpan(DateTime.Now.Ticks - _ticksAtLastKeyDown);
// If the time between the last keydown event and our text change is
// very short, we can be fairly certain than text change was caused
// by the user. We update the _lastText to store their new user input
if (elapsed.TotalMilliseconds <= 5) {
_lastText = Text;
}
else {
// if our last keydown event was more than a few seconds ago,
// it was probably an external change
Text = _lastText;
e.Handled = true;
}
}
base.OnTextChanged(e);
}
}
Here's a sample View Model which I used for testing. It updates its own property 5 times from a separate thread every 10 seconds to simulate a background update from another user.
class ViewModelMain : ViewModelBase, INotifyPropertyChanged
{
private delegate void UpdateText(ViewModelMain vm);
private string _textProperty;
public string TextProperty
{
get { return _textProperty; }
set
{
if (_textProperty != value)
{
_textProperty = value;
RaisePropertyChanged("TextProperty");
}
}
}
public ViewModelMain()
{
TextProperty = "Type here";
for (int i = 1; i <= 5; i++)
{
var sleep = 10000 * i;
var copy = i;
var updateTextDelegate = new UpdateText(vm =>
vm.TextProperty = string.Format("New Value #{0}", copy));
new System.Threading.Thread(() =>
{
System.Threading.Thread.Sleep(sleep);
updateTextDelegate.Invoke(this);
}).Start();
}
}
}
This XAML creates our custom StickyTextBox and a regular TextBox bound to the same property to demonstrate the difference in behavior:
<StackPanel>
<TextBox Text="{Binding TextProperty, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBlock FontWeight="Bold" Margin="5" Text="The 'sticky' text box">
<local:StickyTextBox Text="{Binding TextProperty}" MinWidth="200" />
</TextBlock>
</StackPanel>

Silverlight dependancy property is not notifying in custom control

Scenario
I have a custom combo box where i have a label in the Combobox selection box. I need to change the label as I noted in the second image. But I want to do it only when I select items by selecting the check box. I can select multiple items, so the label should be updated as a comma separated value of selected items. If there is not enough space to display the full text of the label there should be "..." symbol to indicate that there are more items selected in the combo box.
I created a custom Label by inheriting the text Box control where I do all the changes in the callback event of a Dependency property. (Check custom Text Box code)
Now the problem is that the callback event in the custom Text box control is not firing when I change the bounded property in the View model (I am doing this by adding values to the observable collection in the code behind in check box on Check event. Please Check check box event code).
I can see that first time when I load default data in the view model the line is hit by the break point in the "Getter" part of "SelectedFilterResources" . But I never get a hit in the Setter part of the property.
Custom Text Box
The custom text box has the "CaptionCollectionChanged" callback event. It is the place where I have all logic to achieve my scenario. "Resources item" here is a type of Model.
public class ResourceSelectionBoxLable : TextBox
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
IsReadOnly = true;
}
public static List<ResourceItem> LocalFilterdResources = new List<ResourceItem>();
#region Dependancy Properties
public static readonly DependencyProperty FilterdResourcesProperty =
DependencyProperty.Register("SelectedFilterdResources",
typeof (ObservableCollection<ResourceItem>),
typeof (ResourceSelectionBoxLable),
new PropertyMetadata(new ObservableCollection<ResourceItem>(),
CaptionCollectionChanged));
public ObservableCollection<ResourceItem> SelectedFilterdResources
{
get
{
return
(ObservableCollection<ResourceItem>) GetValue(FilterdResourcesProperty);
}
set
{
SetValue(FilterdResourcesProperty, value);
LocalFilterdResources = new List<ResourceItem>(SelectedFilterdResources);
}
}
#endregion
private static void CaptionCollectionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var resourceSelectionBoxLable = d as ResourceSelectionBoxLable;
if (resourceSelectionBoxLable != null)
{
if (LocalFilterdResources.Count <= 0)
{
resourceSelectionBoxLable.Text = "Resources"
}
else
{
var actualwidthOflable = resourceSelectionBoxLable.ActualWidth;
var newValue = e.NewValue as string;
//Get the Wdith of the Text in Lable
TextBlock txtMeasure = new TextBlock();
txtMeasure.FontSize = resourceSelectionBoxLable.FontSize;
txtMeasure.Text = newValue;
double textwidth = txtMeasure.ActualWidth;
//True if Text reach the Limit
if (textwidth > actualwidthOflable)
{
var appendedString = string.Join(", ",
LocalFilterdResources.Select(item => item.ResourceCaption)
.ToArray());
resourceSelectionBoxLable.Text = appendedString;
}
else
{
if (LocalFilterdResources != null)
{
var morestring = string.Join(", ",
(LocalFilterdResources as IEnumerable<ResourceItem>).Select(item => item.ResourceCaption)
.ToArray());
var subsring = morestring.Substring(0, Convert.ToInt32(actualwidthOflable) - 4);
resourceSelectionBoxLable.Text = subsring + "...";
}
}
}
}
}
}
Custom Combo Box.
This is the control where I use the above custom label. This is also a custom control so most of the properties and styles in this controls are custom made. "DPItemSlectionBoxTemplate" is a dependency property where I enable the Selection Box of the combo box by adding an attached property to the control template. This control works fine, because I use this control in other places in my system for different purposes.
<styles:CommonMultiComboBox
x:Name="Resourcescmb" IsEnabled="{Binding IsResourceComboEnable,Mode=TwoWay}"
IsTabStop="False"
>
<styles:CommonMultiComboBox.ItemDataTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelect, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Click="CheckBox_Click"
Content="{Binding ResourceCaption}"
Style="{StaticResource CommonCheckBoxStyle}"
Tag ="{Binding}"
Checked="Resource_ToggleButton_OnChecked" />
</DataTemplate>
</styles:CommonMultiComboBox.ItemDataTemplate>
<styles:CommonMultiComboBox.DPItemSlectionBoxTemplate>
<DataTemplate>
<filtersTemplate:ResourceSelectionBoxLable
Padding="0"
Height="15"
FontSize="10"
SelectedFilterdResources="{Binding DataContext.FilterdResources,ElementName=root ,Mode=TwoWay}" />
</DataTemplate>
</styles:CommonMultiComboBox.DPItemSlectionBoxTemplate>
</styles:CommonMultiComboBox>
ViewModel
private ObservableCollection<ResourceItem> _resourceItems;
public ObservableCollection<ResourceItem> FilterdResources
{
get { return _resourceItems; }
set
{
SetOnChanged(value, ref _resourceItems, "FilterdResources");
}
}
Constructor of View Model
FilterdResources=new ObservableCollection<ResourceItem>();
"SetOnChanged" is a method in the View Model base class where we have the INotifyPropertichanged implementation.
Check Box Event
private void Resource_ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
var senderControl = sender as CheckBox;
if(senderControl==null)
return;
var selectedContent=senderControl.Tag as ResourceItem;
if (selectedContent != null)
{
ViewModel.FilterdResources.Add(selectedContent);
}
}
I can access the View Model from the Code behind through the View Model Property.
Why is the call back event not notified when I change bounded values? Am i missing something here? Dependency properties are supposed to work for two way bindings aren't they? Could any one please help me on this?
Thanks in advance.
Looks like your issue is that you're expecting the CaptionCollectionChanged event to fire when the bound collection is changed (i.e. items added or removed). When in fact this event will fire only when you're changing an instance of the bound object.
What you need to do here is to subscribe to ObservableCollection's CollectionChanged event in the setter or change callback (which you already have - CaptionCollectionChanged) of your dependency property.
public static readonly DependencyProperty FilterdResourcesProperty =
DependencyProperty.Register("SelectedFilterdResources",
typeof (ObservableCollection<ResourceItem>),
typeof (ResourceSelectionBoxLable),
new PropertyMetadata(new ObservableCollection<ResourceItem>(),
CaptionCollectionChanged));
private static void CaptionCollectionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
var sender = d as ResourceSelectionBoxLable;
if (sender != null)
{
collection.CollectionChanged += sender.BoundItems_CollectionChanged;
}
}
}
private void BoundItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Do your control logic here.
}
Don't forget to add cleanup logic - unsubscribe from collection change when collection instance is changed and so on.

How to handle purely view-related commands?

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.

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

Categories