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>
Related
I have a UWP application which uses a PasswordBox control for obscured text entry. The valid characters for the password varies by instance, and is known by the ViewModel layer at runtime. I want to be able to filter invalid characters as the user types them. What is the proper way to do that?
We have similar requirements for regular TextBox controls. We have been able to perform this kind of filtering for text boxes using the TextChanging event and the SelectionStart property for resetting the cursor position. But the PasswordBox does not have either of these.
The XAML for the password box looks like this
<PasswordBox
x:Name="ThePasswordBox"
Grid.Row="1"
MaxLength="{Binding MaxPasswordLength, Mode=OneTime}"
IsEnabled="{Binding IsPasscodeEnabled, Mode=OneWay}"
Password="{Binding Password, FallbackValue=1234, Mode=TwoWay}"
PlaceholderText= "{Binding PlaceholderText, Mode=OneWay}" />
We then respond to the Password.set by checking the validity of the input, and if invalid, resetting to the prior value
set
{
if (_password != value && value != null)
{
// verify that the new password would be valid; if not, roll back
if (!IsPasswordContentAcceptable(value))
{
_passcodeControl.SetPassword(_password);
return;
}
_password = value;
// push the new password to the data binding
_passcodeDataBinding.SetCurrentValue(_password);
// update the UI
HandlePasswordChange();
OnPropertyChanged("Password");
}
}
The call to SetCurrentValue() simply stores the entered password into our Model layer and should not be of consequence to this discussion. The call to _passwordControl.SetPassword updates the Password field on ThePasswordBox:
public void SetPassword(string password)
{
ThePasswordBox.Password = password;
}
HandlePasswordChange() forces other UI elements to re-evaluate, including an OK button that is disabling when the control is invalid. It's implementation should not be important to this question.
The problem with this approach is that when we reset the contents of the PasswordBox (the call to SetPassword, which sets PasswordBox.Password property) the cursor jumps to the first position. So for a numeric password, typing "12a4" would yield "412".
What are our options here?
What's the proper way to filter characters in a UWP PasswordBox?
From your code, you need not call SetPassword method in Password.set, it will make the cursor back the the start position. the better way is bind Password within two way mode. And check the if Password is available. If the Password contains unallowable character then call BackSpace with InputInjector.
public string PassWord
{
get { return _passWord; }
set
{
if(value != null && IsPasswordContentAcceptable(value))
{
_passWord = value;
OnPropertyChanged();
}
else
{
InputInjector inputInjector = InputInjector.TryCreate();
var info = new InjectedInputKeyboardInfo();
info.VirtualKey = (ushort)(VirtualKey.Back);
inputInjector.InjectKeyboardInput(new[] { info });
}
}
}
Xaml
<PasswordBox
x:Name="ThePassWordBox"
MaxLength="20"
Password="{x:Bind PassWord, Mode=TwoWay}"
PlaceholderText="Input your Password"
/>
Alternatively, you can suppress input as it's being received.
In this example, all characters are allowed in the PasswordBox except for "A"
Xaml
<PasswordBox Name="pwBox"></PasswordBox>
Code
public sealed partial class MainPage : Page
{
public MainPage()
{
CoreWindow cw = CoreWindow.GetForCurrentThread();
CoreDispatcher cd = cw.Dispatcher;
cd.AcceleratorKeyActivated += Cd_AcceleratorKeyActivated;
this.InitializeComponent();
}
private void Cd_AcceleratorKeyActivated(CoreDispatcher sender, AcceleratorKeyEventArgs args)
{
if (this.pwBox.FocusState != FocusState.Unfocused)
{
if(args.VirtualKey == Windows.System.VirtualKey.A)
{
args.Handled = true;
}
}
}
}
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.
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
So I'm trying to update my RadRadialGauge. It will display data (using an animated Needle) that is being retrieved on a real-time basis. I have a RadChartView that currently works by using TimeStamp and Value properties to draw the Chart. When I add a chart, sometimes I might want to add a few based on the variable I'm watching. For instance, if I want to watch Motor Speed and Output Frequency, I have to add multiple vertical axes. Part of my code to handle the data binding for the RadChartView is here:
var lineSeries = new LineSeries();
lineSeries.CategoryBinding =
new PropertyNameDataPointBinding() { PropertyName = "TimeStamp" };
lineSeries.ValueBinding =
new PropertyNameDataPointBinding() { PropertyName = "Value" };
lineSeries.ItemsSource = (chart.Chart as GuiAnalogQueue).Records;
The rest of the code is just appearance handling, and then at the end I add the LineSeries to my RadChartView.
Now, I'm trying to port this code, in a way, over to RadGauge. I'm not sure how to bind the values to the Needle so the needle moves when the Value changes.
In the XAML I've tried Value="{Binding Value}" I've tried adding binding to the ValueSource varible. Also I have done needle.Value = chart.Chart.Value;
I can't figure it out, so any help is appreciated.
UPDATE
This is what I'm trying to accomplish. My Records collection has two properties, Value and TimeStamp. I'm trying to bind my Value in the Records to the needle Value. This is my approach to do it programmatically:
public void InitializeCharts(ChartsVM charts, Theme theme)
{
DataContext = charts;
foreach (cVM chart in charts.Charts)
{
Binding value = new Binding();
value.Source = (chart.Chart as GuiAnalogQueue).Records;
value.Path = new PropertyPath("Value");
needle.SetBinding(Needle.ValueProperty, value);
}
}
However, when I do this, it is not changing the needle.Value at all. My Records is the collection that uses NotifyPropertyChanged("Records"), so I would expect my needle to change everytime Records is changed.
As you see in my original post, those three lines take care of binding the variables to ChartView charts, however I can't get the RadGauge to work.
In short, I found that Needle's don't use any type of collections for their Values. So when I tried setting up a Source to be inside of a collection, and a Path, it wasn't really liking that. Instead, I added a property right before I add the Value to the records collection (in my update values function). That way I could set my binding up as:
Binding value = new Binding();
value.Source = (chart.Chart as GuiAnalogQueue);
value.Path = new PropertyPath("AnalogValue");
needle.SetBinding(Needle.ValueProperty, value);
That reads as, the Needle will bind its Value property with the AnalogValue property that is in the Source--chart.Chart as GuiAnalogQueue.
Hope this helps if you've been directed to this page.
Here is a basic example of using the RadRadialGauge.
XAML:
<telerik:RadRadialGauge x:Name="radialGauge"
Width="300"
Height="300"
Grid.Column="0">
<telerik:RadialScale Min="1"
Max="12">
<telerik:RadialScale.Indicators>
<telerik:Needle x:Name="needle" />
<telerik:Pinpoint/>
</telerik:RadialScale.Indicators>
</telerik:RadialScale>
</telerik:RadRadialGauge>
As you can see i have a radial gauge with a radial scale defined. Radial Scale has a needle as the indicator. The RadialScale is from 1 to 12. Note that i have given a name for the needle. We will use this to push values from the code behind.
In this example i am using a dispatcher timer to tick every 1 second and i am generating a random value between 1 to 12. Here is the code behind snippets.
code snippet:
Following variables are declared at the window level
TimeSpan interval = TimeSpan.FromSeconds(1);
DispatcherTimer timer;
Random rnd = new Random();
I have defined event handlers for Window Loaded & Unloaded events. On Window Load, i start the timer.
void OnWindowLoad(object sender, RoutedEventArgs e)
{
timer = new DispatcherTimer();
timer.Interval = interval;
timer.Tick += new EventHandler(Timer_Tick);
timer.Start();
}
Here is the timer tick function:
private void Timer_Tick(object sender, EventArgs e)
{
timer.Stop();
SetNextValue();
timer.Start();
}
Here is the SetNextValue function:
private void SetNextValue()
{
int minValue = 1;
int maxValue = 12;
int nextValue = rnd.Next(minValue, maxValue);
needle.Value = nextValue;
}
In the Unloaded event handler i am stopping the timer.
void OnWindowUnload(object sender, RoutedEventArgs e)
{
timer.Stop();
}
Output:
when you run the app, you will see the needle changing its position because we are generating random numbers from 1 to 12 every second and we set the generated number to needle's value. The SetNextValue() method can be your gateway to monitoring the real value and set the needle value to that real data.
This is the basic example code i can think of to explain the radial gauge.
Hope this provides the answer you are looking for.
Update:
Here is an MVVM way of setting the needle value. Let the window implement INotifyPropertyChanged interface. Set the datacontext to the window itself
public partial class MainWindow : Window, INotifyPropertyChanged
public MainWindow()
{
InitializeComponent();
Loaded += OnWindowLoad;
Unloaded += OnWindowUnload;
DataContext = this;
}
Provide implementation for the PropertyChanged event like below:
public event PropertyChangedEventHandler PropertyChanged = delegate { };
void NotifyPropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
Implement a property called NeedleValue.
int needleValue = 1;
public int NeedleValue
{
get
{
return needleValue;
}
set
{
needleValue = value;
NotifyPropertyChanged("NeedleValue");
}
}
In the SetNextValue - just set the newly created NeedleValue property. this will fire the property changed notification.
private void SetNextValue()
{
int minValue = 1;
int maxValue = 12;
int nextValue = rnd.Next(minValue, maxValue);
NeedleValue = nextValue;
}
In the XAML bind the Needle Value property to NeedleValue like below
<telerik:Needle x:Name="needle" Value="{Binding NeedleValue}" />
Hope this provides you with the answer you are looking for :)
Lohith (Tech Evangelist, Telerik India)
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).