DoEvents: Dispatcher.Invoke vs. PushFrame - wpf

Recently I found a method that performs all pending messages in the dispatcher queue until a specified priority. I already had such code before, but they use completely different methods. Here's both of them:
The PushFrame way:
/// <summary>
/// Enters the message loop to process all pending messages down to the specified
/// priority. This method returns after all messages have been processed.
/// </summary>
/// <param name="priority">Minimum priority of the messages to process.</param>
public static void DoEvents(
DispatcherPriority priority = DispatcherPriority.Background)
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(
priority,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
private static object ExitFrame(object f)
{
((DispatcherFrame) f).Continue = false;
return null;
}
Source: MSDN Library
The blocking Invoke way:
private static Action EmptyDelegate = delegate { };
/// <summary>
/// Processes all pending messages down to the specified priority.
/// This method returns after all messages have been processed.
/// </summary>
/// <param name="priority">Minimum priority of the messages to process.</param>
public static void DoEvents2(
DispatcherPriority priority = DispatcherPriority.Background)
{
Dispatcher.CurrentDispatcher.Invoke(EmptyDelegate, priority);
}
Source: Blog
Which is better and are there any functional differences between the two solutions?
Update: Here's number two with the delegate inlined, as in number one, making it even shorter:
/// <summary>
/// Processes all pending messages down to the specified priority.
/// This method returns after all messages have been processed.
/// </summary>
/// <param name="priority">Minimum priority of the messages to process.</param>
public static void DoEvents2(
DispatcherPriority priority = DispatcherPriority.Background)
{
Dispatcher.CurrentDispatcher.Invoke(new Action(delegate { }), priority);
}

You sort of answered your own question. It doesn't matter much which you pick since both do the same in the background.
Both run into this:
While (dispatcherFrame.Continue)
{
Dispatcher.GetMessage();
Dispatcher.TranslateAndDispatch();
}
However PushFrame is a bit nicer since you do not need to create empty delegate.

Related

How control back stack depth navigation in UWP

How to control back stack depth navigation in UWP? How can I control back stack depth I want my back stack only get 3 page. Actually I want back stack depth always less than 4.
this is my app.xaml.cs
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
rootFrame.CanGoBack ?
AppViewBackButtonVisibility.Visible :
AppViewBackButtonVisibility.Collapsed;
}
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
//save();
deferral.Complete();
}
private void OnBackRequested(object sender, Windows.UI.Core.BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
e.Handled = true;
rootFrame.GoBack();
}
}

MVVM Wait Cursor how to set the.wait cursor during invocation of a command?

Scenario:
User clicks a button on the View
This invokes a command on the ViewModel, DoProcessing
How, and where does the Wait cursor get set, considering the responsibilitues of View and ViewModel?
Just to be clear, I am just looking to change the DEFAULT cursor to an hourglass while the command is running. When the command completes, the cursor mut change back to an arrow. (It is a synchronous operation I am looking for, and I want the UI to block).
I have created an IsBusy property on the ViewModel. How do I ensure that the Application's mouse pointer changes?
I am using it successfully in my application:
/// <summary>
/// Contains helper methods for UI, so far just one for showing a waitcursor
/// </summary>
public static class UIServices
{
/// <summary>
/// A value indicating whether the UI is currently busy
/// </summary>
private static bool IsBusy;
/// <summary>
/// Sets the busystate as busy.
/// </summary>
public static void SetBusyState()
{
SetBusyState(true);
}
/// <summary>
/// Sets the busystate to busy or not busy.
/// </summary>
/// <param name="busy">if set to <c>true</c> the application is now busy.</param>
private static void SetBusyState(bool busy)
{
if (busy != IsBusy)
{
IsBusy = busy;
Mouse.OverrideCursor = busy ? Cursors.Wait : null;
if (IsBusy)
{
new DispatcherTimer(TimeSpan.FromSeconds(0), DispatcherPriority.ApplicationIdle, dispatcherTimer_Tick, System.Windows.Application.Current.Dispatcher);
}
}
}
/// <summary>
/// Handles the Tick event of the dispatcherTimer control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private static void dispatcherTimer_Tick(object sender, EventArgs e)
{
var dispatcherTimer = sender as DispatcherTimer;
if (dispatcherTimer != null)
{
SetBusyState(false);
dispatcherTimer.Stop();
}
}
}
This has been taken from here. Courtsey huttelihut.
You need to call the SetBusyState method every time you think you are going to perform any time consuming operation. e.g.
...
UIServices.SetBusyState();
DoProcessing();
...
This will automatically change your cursor to wait cursor when the application is busy and back to normal when idle.
A very simple method is to simply bind to the 'Cursor' property of the window (or any other control). For example:
XAML:
<Window
x:Class="Example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Cursor="{Binding Cursor}" />
ViewModel Cursor Property (Using Apex.MVVM):
private NotifyingProperty cursor = new NotifyingProperty("Cursor", typeof(System.Windows.Input.Cursor), System.Windows.Input.Cursors.Arrow);
public System.Windows.Input.Cursor Cursor
{
get { return (System.Windows.Input.Cursor)GetValue(cursor); }
set { SetValue(cursor, value); }
}
Then simply change the cursor in your view when needed...
public void DoSomethingLongCommand()
{
Cursor = System.Windows.Input.Cursors.Wait;
... some long process ...
Cursor = System.Windows.Input.Cursors.Arrow;
}
You want to have a bool property in viewmodel.
private bool _IsBusy;
public bool IsBusy
{
get { return _IsBusy; }
set
{
_IsBusy = value;
NotifyPropertyChanged("IsBusy");
}
}
Now you want to set the window style to bind to it.
<Window.Style>
<Style TargetType="Window">
<Setter Property="ForceCursor" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsBusy}" Value="True">
<Setter Property="Cursor" Value="Wait"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
Now whenever a command is being executed and your view model is is busy, it would just set the IsBusy flag and reset it when done. The Window will automatically display the wait cursor and restore the original cursor when done.
You can write the command handler function in view model something like this:
private void MyCommandExectute(object obj) // this responds to Button execute
{
try
{
IsBusy = true;
CallTheFunctionThatTakesLongTime_Here();
}
finally
{
IsBusy = false;
}
}
Command is handled on the view model, so the reasonable decission would be to do folowing:
1) Create a busy indicator service and inject it into the view model (this will allow you to replace the cursor logic with some nasty animation easily)
2) In the command handler call the busy indicator service to notify the user
I might be wrong, but it looks like you are trying to do some heavy calculations or I/O on UI thread. I highly recommend you to perform work on thread pool in this case. You can use Task and TaskFactory to easily wrap work with ThreadPool
There is a great Session(at 50:58) by Laurent Bugnion online (Creator of MVVM Light).
There's also an deepDive session available (alternatively here(at 24:47)).
In at least one of them he live codes a busy Indicator using a is BusyProperty.
The ViewModel should only decide whether it is busy, and the decision about what cursor to use, or whether to use some other technique such as a progress bar should be left up to the View.
And on the other hand, handling it with code-behind in the View is not so desirable either, because the ideal is that Views should not have code-behind.
Therefore I chose to make a class that can be used in the View XAML to specify that the cursor should be change to Wait when the ViewModel is busy. Using UWP + Prism the class definition is:
public class CursorBusy : FrameworkElement
{
private static CoreCursor _arrow = new CoreCursor(CoreCursorType.Arrow, 0);
private static CoreCursor _wait = new CoreCursor(CoreCursorType.Wait, 0);
public static readonly DependencyProperty IsWaitCursorProperty =
DependencyProperty.Register(
"IsWaitCursor",
typeof(bool),
typeof(CursorBusy),
new PropertyMetadata(false, OnIsWaitCursorChanged)
);
public bool IsWaitCursor
{
get { return (bool)GetValue(IsWaitCursorProperty); }
set { SetValue(IsWaitCursorProperty, value); }
}
private static void OnIsWaitCursorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CursorBusy cb = (CursorBusy)d;
Window.Current.CoreWindow.PointerCursor = (bool)e.NewValue ? _wait : _arrow;
}
}
And the way to use it is:
<mvvm:SessionStateAwarePage
x:Class="Orsa.Views.ImportPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mvvm="using:Prism.Windows.Mvvm"
xmlns:local="using:Orsa"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mvvm:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid.RowDefinitions>
.
.
</Grid.RowDefinitions>
<local:CursorBusy IsWaitCursor="{Binding IsBusy}"/>
(other UI Elements)
.
.
</Grid>
</mvvm:SessionStateAwarePage>
IMHO that it is perfectly fine for the wait cursor logic to be next to the command in the viewmodel.
As to the best way to do change the cursor, create a IDisposable wrapper that changes the Mouse.OverrideCursor property.
public class StackedCursorOverride : IDisposable
{
private readonly static Stack<Cursor> CursorStack;
static StackedCursorOverride()
{
CursorStack = new Stack<Cursor>();
}
public StackedCursorOverride(Cursor cursor)
{
CursorStack.Push(cursor);
Mouse.OverrideCursor = cursor;
}
public void Dispose()
{
var previousCursor = CursorStack.Pop();
if (CursorStack.Count == 0)
{
Mouse.OverrideCursor = null;
return;
}
// if next cursor is the same as the one we just popped, don't change the override
if ((CursorStack.Count > 0) && (CursorStack.Peek() != previousCursor))
Mouse.OverrideCursor = CursorStack.Peek();
}
}
Usage:
using (new StackedCursorOverride(Cursors.Wait))
{
// ...
}
The above is a revised version of the solution that I posted to this question.
private static void LoadWindow<T>(Window owner) where T : Window, new()
{
owner.Cursor = Cursors.Wait;
new T { Owner = owner }.Show();
owner.Cursor = Cursors.Arrow;
}

In Silverlight, how do I find the first field bound to a model that has an error so that I can give it focus?

Greetings
I have a Silverlight form bound to a model object which implements INotifyDataErrorInfo and does validation when you click the save button. If some of the properties on the model come back invalid, Silverlight will automatically highlight the bound input field.
Is there a way to set the focus to the first invalid field?
UPDATE:
Is there even a way to see if an input field is in that invalid display state? If I can detect that, I can loop through my fields and set the focus manually.
Thanks,
Matthew
You could use a ValidationSummary in your view to display all validation errors your model raised. When you click on an error in the ValidationSummary the control which caused the validation error will be focused.
An example of the ValidationSummary can be found on the samples of the Silverlight Toolkit.
Until now I didnĀ“t use the ValidationSummary in any application, so i cannot provide you any informations about usage or "how to use", but maybe this will help you
I've implemented this behavior.
First you need to subscribe to your ViewModel ErrorsChanged and PropertyChanged methods. I am doing this in my constructor:
/// <summary>
/// Initializes new instance of the View class.
/// </summary>
public View(ViewModel viewModel)
{
if (viewModel == null)
throw new ArgumentNullException("viewModel");
// Initialize the control
InitializeComponent(); // exception
// Set view model to data context.
DataContext = viewModel;
viewModel.PropertyChanged += new PropertyChangedEventHandler(_ViewModelPropertyChanged);
viewModel.ErrorsChanged += new EventHandler<DataErrorsChangedEventArgs>(_ViewModelErrorsChanged);
}
Then write handlers for this events:
/// <summary>
/// If model errors has changed and model still have errors set flag to true,
/// if we dont have errors - set flag to false.
/// </summary>
/// <param name="sender">Ignored.</param>
/// <param name="e">Ignored.</param>
private void _ViewModelErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
if ((this.DataContext as INotifyDataErrorInfo).HasErrors)
_hasErrorsRecentlyChanged = true;
else
_hasErrorsRecentlyChanged = false;
}
/// <summary>
/// Iterate over view model visual childrens.
/// </summary>
/// <param name="sender">Ignored.</param>
/// <param name="e">Ignored.</param>
private void _ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if ((this.DataContext as INotifyDataErrorInfo).HasErrors)
_LoopThroughControls(this);
}
And finally add method:
/// <summary>
/// If we have error and we haven't already set focus - set focus to first control with error.
/// </summary>
/// <remarks>Recursive.</remarks>
/// <param name="parent">Parent element.</param>
private void _LoopThroughControls(UIElement parent)
{
// Check that we have error and we haven't already set focus
if (!_hasErrorsRecentlyChanged)
return;
int count = VisualTreeHelper.GetChildrenCount(parent);
// VisualTreeHelper.GetChildrenCount for TabControl will always return 0, so we need to
// do this branch of code.
if (parent.GetType().Equals(typeof(TabControl)))
{
TabControl tabContainer = ((TabControl)parent);
foreach (TabItem tabItem in tabContainer.Items)
{
if (tabItem.Content == null)
continue;
_LoopThroughControls(tabItem.Content as UIElement);
}
}
// If element has childs.
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
if (child is System.Windows.Controls.Control)
{
var control = (System.Windows.Controls.Control)child;
// If control have error - we found first control, set focus to it and
// set flag to false.
if ((bool)control.GetValue(Validation.HasErrorProperty))
{
_hasErrorsRecentlyChanged = false;
control.Focus();
return;
}
}
_LoopThroughControls(child);
}
}
}

WPF Validation: Clearing all validation errors

I have a WPF UserControl with many other controls inside of it.
TextBoxes are among these.
Every TextBox has its own validation:
<TextBox>
<TextBox.Text>
<Binding Path="MyPath" StringFormat="{}{0:N}" NotifyOnValidationError="True">
<Binding.ValidationRules>
<r:MyValidationRule ValidationType="decimal" />
</Binding.ValidationRules>
</Binding>
<TextBox.Text>
<TextBox>
a
Now suppose the user types some invalid characters into them. They will all become highlighted red.
Now I want to reset all the validation errors (from the incorrect input) and set the recent correct values coming from DataContext.
I set the DataContext in the constructor and I don't want to change it (DataContext = null won't help me then):
DataContext = _myDataContext = new MyDataContext(..);
What I've already found are these classes:
Validation.ClearInvalid(..)
BindingExpression.UpdateTarget();
I think these classes could help me, but they require the Binding of a concrete FrameworkElement and I want to do it globally for all of them.
Should I anyhow iterate through the Visual Tree (which is really what I don't like) or is there any better solution for this?
This is what a BindingGroup is for... You'd set a BindingGroup on a container of all the controls, e.g. the panel that contains them. This would cause the updates to the DataContext to be held until you call UpdateSources on the BindingGroup. If you want to reset the user's input, you'd call CancelEdit instead, and the BindingGroup would reset all controls inside the container to the (still unchanged) values of the DataContext.
I had the same problem. Multiple validated controls on a page. I found/made this solution to update (and clear all validation from) the descentents of a DependencyObject:
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
/// <summary>
/// Updates all binding targets where the data item is of the specified type.
/// </summary>
/// <param name="root">The root.</param>
/// <param name="depth">The depth.</param>
/// <param name="dataItemType">Type of the data item.</param>
/// <param name="clearInvalid">Clear validation errors from binding.</param>
public static void UpdateAllBindingTargets(this DependencyObject root, int depth, Type dataItemType, bool clearInvalid)
{
var bindingExpressions = EnumerateDescendentsBindingExpressions(root, depth);
foreach (BindingExpression be in bindingExpressions.Where(be => be.DataItem != null && be.DataItem.GetType() == dataItemType))
{
if (be != null)
{
be.UpdateTarget();
if (clearInvalid)
System.Windows.Controls.Validation.ClearInvalid(be);
}
}
}
/// <summary>
/// Enumerates all binding expressions on descendents.
/// </summary>
/// <param name="root">The root.</param>
/// <param name="depth">The depth.</param>
/// <returns></returns>
public static IEnumerable<BindingExpression> EnumerateDescendentsBindingExpressions(this DependencyObject root, int depth)
{
return root.EnumerateDescendents(depth).SelectMany(obj => obj.EnumerateBindingExpressions());
}
/// <summary>
/// Enumerates the descendents of the specified root to the specified depth.
/// </summary>
/// <param name="root">The root.</param>
/// <param name="depth">The depth.</param>
public static IEnumerable<DependencyObject> EnumerateDescendents(this DependencyObject root, int depth)
{
int count = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(root, i);
yield return child;
if (depth > 0)
{
foreach (var descendent in EnumerateDescendents(child, --depth))
yield return descendent;
}
}
}
/// <summary>
/// Enumerates the binding expressions of a Dependency Object.
/// </summary>
/// <param name="element">The parent element.</param>
public static IEnumerable<BindingExpression> EnumerateBindingExpressions(this DependencyObject element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
LocalValueEnumerator lve = element.GetLocalValueEnumerator();
while (lve.MoveNext())
{
LocalValueEntry entry = lve.Current;
if (BindingOperations.IsDataBound(element, entry.Property))
{
if (entry.Value is PriorityBindingExpression)
{
foreach (BindingExpression expr in ((PriorityBindingExpression)entry.Value).BindingExpressions)
yield return expr;
}
else if (entry.Value is MultiBindingExpression)
{
foreach (BindingExpression expr in ((MultiBindingExpression)entry.Value).BindingExpressions)
yield return expr;
}
else
yield return entry.Value as BindingExpression;
}
}
}
Why won't you just trigger NotifyPropertyChanged for all the properties of your data source? This will update binding and UI controls should get values from datacontext (which are valid, thus validation errors will be cleared)?
I'm not sure what you mean by
I set the DataContext in the constructor and I don't want to change it
(DataContext = null won't help me then)
Generally to reset all bindings on the form you do the following: (assuming a controller for views/viewmodel wiring, otherwise just use a code-behind on the view.)
var dataContext = view.DataContext;
view.DataContext = null;
view.DataContext = dataContext;
It doesn't change it to a new data context, it just drops the data context and reloads it. This kicks off all of the bindings to re-load.
Although hbarck gave a perfectly correct answer, I would just like to add that for many standard WPF controls, BindingGroups are created automatically. Therefore, in most cases, the following simple code is enough for clearing all validation errors inside some control (for example, DataGrid):
foreach (var bg in BindingOperations.GetSourceUpdatingBindingGroups(myDataGrid))
bg.CancelEdit();

Validation in textbox in WPF

I am currently working on a WPF application where I would like to have a TextBox that can only have numeric entries in it. I know that I can validate the content of it when I lost the focus and block the content from being numeric, but in other Windows Form application, we use to totally block any input except numerical from being written down. Plus, we use to put that code in a separate dll to reference it in many places.
Here is the code in 2008 not using WPF:
Public Shared Sub BloquerInt(ByRef e As System.Windows.Forms.KeyPressEventArgs, ByRef oTxt As Windows.Forms.TextBox, ByVal intlongueur As Integer)
Dim intLongueurSelect As Integer = oTxt.SelectionLength
Dim intPosCurseur As Integer = oTxt.SelectionStart
Dim strValeurTxtBox As String = oTxt.Text.Substring(0, intPosCurseur) & oTxt.Text.Substring(intPosCurseur + intLongueurSelect, oTxt.Text.Length - intPosCurseur - intLongueurSelect)
If IsNumeric(e.KeyChar) OrElse _
Microsoft.VisualBasic.Asc(e.KeyChar) = System.Windows.Forms.Keys.Back Then
If Microsoft.VisualBasic.AscW(e.KeyChar) = System.Windows.Forms.Keys.Back Then
e.Handled = False
ElseIf strValeurTxtBox.Length < intlongueur Then
e.Handled = False
Else
e.Handled = True
End If
Else
e.Handled = True
End If
Is there an equivalent way in WPF? I wouldn't mind if this is in a style but I am new to WPF so style are a bit obscure to what they can or can't do.
You can restrict the input to numbers only using an attached property on the TextBox. Define the attached property once (even in a separate dll) and use it on any TextBox. Here is the attached property:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
/// <summary>
/// Class that provides the TextBox attached property
/// </summary>
public static class TextBoxService
{
/// <summary>
/// TextBox Attached Dependency Property
/// </summary>
public static readonly DependencyProperty IsNumericOnlyProperty = DependencyProperty.RegisterAttached(
"IsNumericOnly",
typeof(bool),
typeof(TextBoxService),
new UIPropertyMetadata(false, OnIsNumericOnlyChanged));
/// <summary>
/// Gets the IsNumericOnly property. This dependency property indicates the text box only allows numeric or not.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to get the property from</param>
/// <returns>The value of the StatusBarContent property</returns>
public static bool GetIsNumericOnly(DependencyObject d)
{
return (bool)d.GetValue(IsNumericOnlyProperty);
}
/// <summary>
/// Sets the IsNumericOnly property. This dependency property indicates the text box only allows numeric or not.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> to set the property on</param>
/// <param name="value">value of the property</param>
public static void SetIsNumericOnly(DependencyObject d, bool value)
{
d.SetValue(IsNumericOnlyProperty, value);
}
/// <summary>
/// Handles changes to the IsNumericOnly property.
/// </summary>
/// <param name="d"><see cref="DependencyObject"/> that fired the event</param>
/// <param name="e">A <see cref="DependencyPropertyChangedEventArgs"/> that contains the event data.</param>
private static void OnIsNumericOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool isNumericOnly = (bool)e.NewValue;
TextBox textBox = (TextBox)d;
if (isNumericOnly)
{
textBox.PreviewTextInput += BlockNonDigitCharacters;
textBox.PreviewKeyDown += ReviewKeyDown;
}
else
{
textBox.PreviewTextInput -= BlockNonDigitCharacters;
textBox.PreviewKeyDown -= ReviewKeyDown;
}
}
/// <summary>
/// Disallows non-digit character.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="TextCompositionEventArgs"/> that contains the event data.</param>
private static void BlockNonDigitCharacters(object sender, TextCompositionEventArgs e)
{
foreach (char ch in e.Text)
{
if (!Char.IsDigit(ch))
{
e.Handled = true;
}
}
}
/// <summary>
/// Disallows a space key.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="KeyEventArgs"/> that contains the event data.</param>
private static void ReviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
{
// Disallow the space key, which doesn't raise a PreviewTextInput event.
e.Handled = true;
}
}
}
Here is how to use it (replace "controls" with your own namespace):
<TextBox controls:TextBoxService.IsNumericOnly="True" />
You can put a validation in your binding
<TextBox>
<TextBox.Text>
<Binding Path="CategoriaSeleccionada.ColorFondo"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<utilities:RGBValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Look at this example (of my program), you put the validation inside the binding like this. With UpdateSourceTrigger you can change when you binding will be updated (lost focus, in every change...)
Well, the validation is a class, I will put you an example:
class RGBValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
// Here you make your validation using the value object.
// If you want to check if the object is only numbers you can
// Use some built-in method
string blah = value.ToString();
int num;
bool isNum = int.TryParse(blah, out num);
if (isNum) return new ValidationResult(true, null);
else return new ValidationResult(false, "It's no a number");
}
}
In short, do the job inside that method and return a new ValidationResult. The first parameter is a bool, true if the validation is good, false if not. The second parameter is only a message for information.
I think that this is the basics of textbox validation.
Hope this help.
EDIT: Sorry, I don't know VB.NET but I think that the C# code is pretty simple.

Resources