I've been trying to create my generic ReadOnlyCheckBox style/template but I'm having a problem with the binding to the data. In the example here:
A read-only CheckBox in C# WPF
you bind directly to the data from the ControlTemplate definition, but of course this is not really what I want, as I want to be able to declare the new checkbox something like this:
<CheckBox x:Name="uiComboBox" Content="Does not set the backing property, but responds to it."
Style="{StaticResource ReadOnlyCheckBoxStyle}" IsChecked="{Binding MyBoolean}" Click="uiComboBox_Click"/>
Except of course when I do this and then set the event trigger on the bullet to be a TemplateBinding of IsChecked I have exactly what I started with! I guess I don't understand why setting the binding directly in the bullet is different from setting IsChecked and then binding to that, isn't the TemplateBinding just a way of referencing what is set in the properties of the control being created? How is the Click triggering the UI update even tho the data does not get updated? Is there a trigger for Click I can override to stop the update?
I got all the DictionaryResource stuff working fine so I am happy with that, cheers for the pointer.
The other thing I was curious about was if it is possible to reduce my Control/Style template by using the BasedOn parameter in the style, then I would only override the things I actually need to change rather than declaring a lot of stuff that I think is part of the standard template anyway. I might have a play with this.
Cheers
ed
The problem you're running into is that you're trying to use DataBinding where you shouldn't.
I disagree with other answers you've been getting in the link you've posted. While the ControlTemplate solutions appear neat and tidy, they don't get at the heart of your problem, which is, you're trying to use a single control (a CheckBox) to do two different things: show state (e.g. checked/unchecked) and perform logic (remote device, etc.).
ControlTemplates are designed to change the way a control appears, but not behaves. You're trying to change behavior, not appearance. See "What Is a ControlTemplate?" here for more details
To do that, you're going to have to store some forego the standard binding and handle some events:
XAML:
<Window x:Class="WPFCheckBoxClickTester.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<CheckBox x:Name="foo" Checked="foo_Checked"></CheckBox>
</Grid>
</Window>
Code-Behind:
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace WPFCheckBoxClickTester
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
private BackgroundWorker _worker = new BackgroundWorker();
public Window1()
{
InitializeComponent();
foo.IsThreeState = false;
this._worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
this._worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
}
private void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
foo.IsChecked = true;
foo.IsEnabled = true;
return;
}
private void _worker_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(500);
return;
}
private void foo_Checked(object sender, RoutedEventArgs e)
{
if( foo.IsChecked == true && this.foo.IsEnabled )
{
this.foo.IsChecked = false;
this.foo.IsEnabled = false;
this._worker.RunWorkerAsync();
}
return;
}
}
}
The above code example gives you the ability to execute some code async via BackgroundWorker -- I've put in a Thread.Sleep as a placeholder. I'm also using foo.IsEnabled as a sentinel value in foo_Checked, but you may need some extra state here to handle all of your cases (for instance, going from Checked to Unchecked).
Related
I am very new to AvaloniaUI.
I am really struggling to change a text when I click a button.
Here is my code:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReadyForWar_Launcher.MainWindow"
Title="ReadyForWar_Launcher">
<StackPanel>
<TextBlock Name="TestBlock">Show my text here!</TextBlock>
<Button Command="{Binding RunTheThing}" CommandParameter="Hello World">Change the Text!</Button>
</StackPanel>
</Window>
Here is my MainWindow.xaml.cs:
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ReadyForWar_Launcher
{
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void RunTheThing()
{
}
}
}
Inside RunTheThing I don't know how can I select the TextBlock with Name="TestBlock" and change the text to "Hello World".
Can you please help me out on this ?
There are two approaches, the recommended one and straightforward one.
Recommended: Use MVVM pattern. Create a view model with ButtonTextProperty and RunTheThing command, make the command to change the property, assign that model to the DataContext and bind your button text and command to view model properties. The MVVM approach is basically the same as in WPF, so you can use documentation and tutorials from there (that applies to most of the Avalonia, BTW). For example, here is a good one (not advertising, 4th link from google).
Straightforward (aka winforms-way): add x:Name="MyButton" to your button and use this.FindControl<Button>("MyButton") after calling AvaloniaXamlLoader.Load(this);. This will give you a Button reference that you can manipulate from code. Instead of using commands, you can just subscribe to the click handler directly from codebehind, add public void MyButton_OnClick(object sender, RoutedEventArgs args){} to your MainWindow class and add replace Command and CommandParameter with Click="MyButton_OnClick". That way button click will trigger your event handler.
Note, that the second approach doesn't scale well with the application size and suffers from code complexity when handling lists.
I have a little question. I'm trying to create an UWP C# application, and I'm using toggle switches in the project. If I toggle the status of the ToggleSwitch from the software very often, the memory usage increases very much. Why is this happening?
The ToggleSwitch represents a boolean using binding.
I have created example code:
XAML:
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ToggleSwitch Header="Test" HorizontalAlignment="Left" Margin="213,27,0,0" VerticalAlignment="Top" IsOn="{Binding Path=TestBool, Mode=TwoWay}"/>
<CheckBox Content="Two-state CheckBox" Margin="108,162,0,806" IsChecked="{Binding Path=TestBool, Mode=TwoWay}"/>
<Button Content="Start!" HorizontalAlignment="Left" Margin="69,58,0,0" VerticalAlignment="Top" Click="Button_Click"/>
</Grid>
C#:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using System.ComponentModel;
using System.Runtime.CompilerServices;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace App1
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
///
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private bool testBool = false;
public bool TestBool { get { return testBool; } set { if (testBool != value) { testBool = value; OnPropertyChanged(); } } }
public MainPage()
{
DataContext = this;
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10000; i++)
{
TestBool = !TestBool;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Do I do something that is not possible, what do I understand wrong?
When I replace the ToggleSwitch with a CheckBox, the memory usage increases much less.
It seems that a memory leak is occurring on your application, explaining why the GC failed to free it up.
Some considerations:
Why are you implementing INotifyPropertyChanged on the page level? You should implement it on a separate class. That class should hold the model and implement the INotifyPropertyChanged interface itself.
That class is commonly denominated as a ViewModel, and as the name suggests holds the model for a View and it knows how to communicate any changes onto it.
Generally most of the problems with memory leaks with Bindings, that have been reported happen generally when utilizing compiled bindings, {x:Bind ...}, but you are utilizing the traditional bindings. You've correctly performed binding onto a dependency property, such as IsOn and also implemented the INotifyPropertyChanged on the source object, so I don't think that exists something inherently wrong with how you are spinning up the binding process.
You are performing 100 bindings update in a row, so even if the CPU usage should be a bit lower, it is expected that while you are iterating over the loop you will have your CPU usage increased.
Ps: Since you have not marked your method as `async, you will be blocking the UI thread until your loop operation ends, which might be something that you could want to change.
A binding update, is certainly a process with a couple of actions in the middle for each that's why I'm "confident" that the CPU usage might not be a big issue.
I don't actually have a guess, but I would strongly suggest to refactor your code to have the ViewModel class implemented as an extra class, and observe if the memory leaks observed seize to exist.
Looks like your invoke is draining CPU.
My answer is you should think again about your app's architecture.
Im new to MVVM and try to follow all the guidelines I find to respect it. I would like to have a Busy-Animation on one of my usercontrols. I want to include it on the control like this.
The Usercontrol it is nested in is shown on the MainWindow using a DataTemplate for a ViewModel, for example like so:
<Window.Resources>
<DataTemplate DataType="{x:Type AppViews:AppConfigViewModel}">
<local:AppConfigView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</Grid>
When running this, the Application is shown and I also see the view for the AppConfigViewModel which is bind correctly since underlying values are displayed correctly in the view.
Now I tried to register the Busy-Animation in the ViewModel (to control it from there) by doing this in the Constructor of the BusyAnimation:
(DataContext as PageViewModel).BusyAnim = this;
For some reason the DataContext is always null and the result of this line is an exception. What am I doing wrong here?
What I tried to did there is against the idea of MVVM.
I tried downcasting an object that is meant to be general.
A better aproach for the task I tried to achieve is implementing dependency properties in the busy animation component. Those are meant to be bound to from the viewmodel of the mainly displayed view. that way the busy animation can be shown when some property in the viewmodel changes. That could be for example a bool with the name "working".
this is the dependency property code in my busy animation:
public static readonly DependencyProperty ShowBusyProperty = DependencyProperty.Register("ShowBusy", typeof(Boolean), typeof(FortschrittView), new PropertyMetadata(false, OnShowBusyPropertyChanged));
public Boolean ShowBusy
{
get { return (Boolean)this.GetValue(ShowBusyProperty); }
set { this.SetValue(ShowBusyProperty, value); }
}
private static void OnShowBusyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
FortschrittView myUserControl = dependencyObject as FortschrittView;
myUserControl.OnPropertyChanged("ShowBusy");
myUserControl.OnShowBusyPropertyChanged(e);
}
private void OnShowBusyPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if(ShowBusy)
{
Start();
}
else
{
Stop();
}
}
Yes its a lot of code, but I feel wpf wants it that way. Remember above code is in the busy-animation user control and triggers Start() Stop() functions which control storyboards.
Below xaml is in the control that uses the busyanimation, binding it to a viewmodel that the busy-animation should indicate background-work for:
<local:BusyAnimation ShowBusy="{Binding Model.IsBusy}"/>
That ShowBusy Property there is the Dependency Property implemented above. Of course IsBusy from the model should follow the observable pattern for everything to work.
/ps: I throughoutly documented the mistakes i did and how i solved them. Can I get rid of the negative points I got somehow for creating this question?
I have a wpf Custom Control on which I have been working. It has a shared New like this:
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in themes\generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(GetType(VtlDataNavigator_24)))
ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub
If an Items source has been set for the custom control this shared sub then invokes the overrideMetadata for the itemssource (as shown below)
Private Shared Sub OnItemsSourceHasChanged(ByVal d As DependencyObject, ByVal baseValue As Object)
Dim vdn As VtlDataNavigator_24 = DirectCast(d, VtlDataNavigator_24)
vdn.RecordCount = vdn.Items.SourceCollection.Cast(Of Object)().Count()
vdn.MyBaseCollection = DirectCast(vdn.ItemsSource, ICollectionView)
vdn.MyBaseEditableCollection = DirectCast(vdn.ItemsSource, IEditableCollectionView)
vdn.MyBaseCollection.MoveCurrentToFirst
vdn.RecordIndex = vdn.MyBaseCollection.CurrentPosition + 1
If Not IsNothing(vdn.FindButton) Then
If vdn.FindButton.Visibility = Visibility.Visible Then
vdn.RecordIndexTextBox.IsReadOnly = False
Else
vdn.RecordIndexTextBox.IsReadOnly = True
End If
End If
vdn.ResetTheNavigationButtons
vdn.SetupInitialStatesForNonNavigationButtons
End Sub
This then fails because buttons referred to in the code (and routines called from it) have not yet been instantiated because the override for OnApplyTemplate (shown below) has not been called.
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
RecordIndexTextBox = CType(GetTemplateChild("PART_RecordIndexTextBox"), TextBox)
RecordCountTextBox = CType(GetTemplateChild(RecordCountTextBoxPart), TextBox)
RecordTextBlock = CType(GetTemplateChild(RecordTextBlockPart), TextBlock)
OfTextBlock = CType(GetTemplateChild(OfTextBlockPart), TextBlock)
FirstButton = CType(GetTemplateChild(FirstButtonPart), Button)
PreviousButton = CType(GetTemplateChild(PreviousButtonPart), RepeatButton)
NextButton = CType(GetTemplateChild(NextButtonPart), RepeatButton)
LastButton = CType(GetTemplateChild(LastButtonPart), Button)
AddButton = CType(GetTemplateChild(AddButtonPart), Button)
CancelNewRecordButton = CType(GetTemplateChild(CancelNewButtonPart), Button)
EditButton = CType(GetTemplateChild(EditButtonPart), button)
CancelButton = CType(GetTemplateChild(CancelButtonPart), Button)
RefreshButton = CType(GetTemplateChild(RefreshButtonPart), Button)
SaveButton = CType(GetTemplateChild(SaveButtonPart), Button)
DeleteButton = CType(GetTemplateChild(DeleteButtonPart), Button)
FindButton = CType(GetTemplateChild(FindButtonPart), Button)
End Sub
If I add something along the lines of:
vdn.OnApplyTemplate
to OnItemsSourceHasChanged, OnApplyTemplate is called but nothing is resolved (see illustration below).
BUT if I don't set an itemssource on my control, then OnApplyTemplate gets called and the items resolve (see below)
Has anyone encountered this sort of behaviour before and found a way to correct it such that OnApplyTemplate is always the first thing to get called before anything that might require access to controls that have yet to be resolved.
Edit
The curious thing about this issue is that (and doesn't this always seem to be the case!) this was working until obviously I did something or set some property. What I am left with is a project that runs if I do not set an Items source on my custom control, and one which doesn't if I do because the custom handler I have in place to handle when the items source is changed on my custom control is running before OnApplyTemplate gets called.
Well I have at last been able to determine that my custom controls Itemssource property is being changed before the control is being drawn and rendered and therefore the code I have in place to set things up following the ItemsSource change raises null reference exceptions because the main control has yet to be rendered.
Given that it did work it must be something I've done but I'm now out od ideas as to how to delve into this further and actually find the reason. I'd welcome any suggestions you might have or potential work rounds.
Edit in relation to comments below: typical part of control template.
<!-- First Button -->
<Button Style="{StaticResource vtlNavButtonStyle}"
x:Name="PART_FirstButton"
Tag="First_Button"
Visibility="{Binding Path=NavigationButtonVisibility,Converter={StaticResource booltovis}, RelativeSource={RelativeSource TemplatedParent}}"
ToolTipService.ShowOnDisabled="False"
ToolTipService.ShowDuration="3000"
ToolTipService.InitialShowDelay="500">
<Button.ToolTip>
<Binding Path="FirstButtonToolTip"
RelativeSource="{RelativeSource TemplatedParent}"
TargetNullValue="{x:Static p:Resources.FirstText}">
</Binding>
</Button.ToolTip>
<StackPanel>
<Image Style="{StaticResource vtlImageStyle}">
<Image.Source>
<Binding Path="FirstImage"
RelativeSource="{RelativeSource TemplatedParent}">
<Binding.TargetNullValue>
<ImageSource>/VtlWpfControls;component/Images/16/first.png</ImageSource>
</Binding.TargetNullValue>
</Binding>
</Image.Source>
</Image>
</StackPanel>
</Button>
Calling OnApplyTemplate yourself isn't going to help; the framework will call it when the template has actually been applied. That said, the order in which things happen is not deterministic -- the template may or may not be applied before the ItemsSource is set. I'm working with UWP apps for Windows 10, which is a slightly different beast, but we've solved a similar issue doing something like this:
private TextBlock textBlock;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Grab the template controls, e.g.:
textBlock = GetTemplateChild("MyTextBlock") as TextBlock;
InitializeDataContext();
DataContextChanged += (sender, args) => InitializeDataContext();
}
private void InitializeDataContext()
{
ViewModel ViewModel = DataContext as ViewModel;
if (viewModel != null)
{
// Here we know that both conditions are satisfied
textBlock.Text = ViewModel.Name;
}
}
The key is to not start listening for DataContextChanged until the template has been applied. If the data context has already been set, the first call to initializeDataContext takes care of things; if not, the callback takes care of things.
(In your case, replace our data context listening with items source listening, I suppose.)
This isn't an answer to your question, but instead expands on some things you mentioned in the comments.
I really think that it would benefit you to look into WPF commands as they pertain to custom controls. Your data navigator control sounds like it essentially supports a number of actions (go to first/previous/next/last; add; edit; cancel; etc) that you invoke using Button controls in the control template. Rather than looking for the buttons in OnApplyTemplate (at which point you store references to them so that you can presumably hook into their Click event later) you should support commands in your control: the buttons in the template would then bind to these commands.
An example would probably make this a bit clearer. The following is code for a custom control that supports two actions: go-to-first-page, and go-to-last-page. In the static constructor I register two command bindings, one for each action. These work by calling into a helper method that takes the command to "bind" to, plus a pair of delegates that get called when the action is invoked.
The commands I am using here are provided by the WPF framework, and are static properties contained in the static NavigationCommands class. (There are a bunch of other similar classes containing commands, just follow the links in the "See Also" section of that MSDN page).
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace StackOverflow
{
public class TestControl : Control
{
static TestControl()
{
RegisterCommandBinding<TestControl>(NavigationCommands.FirstPage,
x => x.GoToFirstPage());
RegisterCommandBinding<TestControl>(NavigationCommands.LastPage,
x => x.GoToLastPage(), x => x.CanGoToLastPage());
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl),
new FrameworkPropertyMetadata(typeof(TestControl)));
}
void GoToFirstPage()
{
Console.WriteLine("first page");
}
void GoToLastPage()
{
Console.WriteLine("last page");
}
bool CanGoToLastPage()
{
return true; // Would put your own logic here obviously
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute) where TControl : class
{
RegisterCommandBinding<TControl>(command, execute, target => true);
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute, Func<TControl, bool> canExecute)
where TControl : class
{
var commandBinding = new CommandBinding(command,
(target, e) => execute((TControl) target),
(target, e) => e.CanExecute = canExecute((TControl) target));
CommandManager.RegisterClassCommandBinding(typeof(TControl), commandBinding);
}
}
}
The following is the control's default template. As you can see there are simply two Button controls, each one of which binds to the relevant command via its Command property (note this is not a data binding, ie. you're not using the {Binding} markup extension).
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<Style TargetType="{x:Type local:TestControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<StackPanel Orientation="Horizontal">
<Button Command="NavigationCommands.FirstPage" Content="First" />
<Button Command="NavigationCommands.LastPage" Content="Last" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Finally, here's the custom control in a Window. As you click the "First" and "Last" buttons you can see the actions being invoked by watching the relevant text appear in the debug console window.
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<local:TestControl VerticalAlignment="Top" />
</Window>
If you use commands in this way then you should be able to simplify your control's code significantly.
I had a similar issue - a custom control (specifically, a class derived from Control) would show binding errors whenever a new instance of the control was instantiated. This was because the control template was being created before the bindings were setup. Once the bindings took effect, then the control would start to work.
To "fix" this (or work around it anyway) I just added a call to ApplyTemplate() to the control's constructor. So it ends up looking like this:
public CustomControl()
{
InitializeComponent();
ApplyTemplate();
}
Then there were no more binding errors.
I am creating a custom control that when invoked in the XAML can be set to only allow certain types of inputs:
<lib:CustomControl RestrictTo="UnsignedIntegersOnly" ... ></CustomControl>
Where the UnsignedIntegersOnly is part of an Enum containing the set of allowed restrictions.
If the user inputs something that is not allowed, the control will throw a validation error and not allow him to continue to the next form/page/etc.
My vision for implementing this, was to, in the underlying TextBox that makes up this control, bind its text field to a validation rule which will be passed as an input the RestrictTo value that was specified in the CustomControl XAML declaration. Then in that ValidationRule class, handle the RestrictTo specific validation and return whether the validation was successful or not.
This is where I am not quite sure how to proceed. Is it even possible to pass arguments to the ValidationRule in such a seemingly dynamic manner? I am setting a property, RestrictTo, of my control and then passing that to its validation.
If it is possible, how would it be done? What sort of binding or resource linking should I use?
You might be interested in using a MaskedTextBox control, it will restrict what the user can input in the TextBox.
As there's no official control from Microsoft for WPF I would suggest the following from Xceed :
MaskedTextBox (it's free to use :-)
Here you have the syntax of the mask :
http://msdn.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask.aspx
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="350" Width="525">
<Grid>
<xctk:MaskedTextBox Mask="0000"></xctk:MaskedTextBox>
</Grid>
</Window>
If you have Visual Studio 2012 you can easily get this package through NuGet :
(right-click on your project )
Note : on my answer to your previous question, I extensively used validation rules in the link I posted but I'd say that if there are some times where you can avoid it through the means of a well-crafted component/control, then it's wise to do so. As #Barn pointed out on your previous question, a generic validation rule might be a hard thing to do and somewhat questionable as you'll have to handle all the types in it, IMO it's a little counter-intuitive as validators and converters are generally specific against being generalist; you're likely to waste more time on it than it's worth and it will be probably less re-usable than you think it could be. (source : my experience)
Below code should get you started. It is a user control and not a custom control. I recomend you get your code working as a user control first and then convert it to a custom control. Bind Valid property to some property in your viewmodel that controls user workflow.
XAML:
<UserControl x:Class="WpfApplication.ValidatingControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" >
<StackPanel Orientation="Horizontal">
<TextBox Name="_textBox" TextChanged="OnTextChanged" Background="LightGray" Width="200"/>
<TextBlock Name="_messageText" Foreground="Red" />
</StackPanel>
</UserControl>
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication
{
public partial class ValidatingControl : UserControl
{
public ValidatingControl()
{
InitializeComponent();
}
public enum Restrictions
{
UnsignedIntegersOnly,
SmallIntegersOnly
}
public static readonly DependencyProperty RestrictToProperty =
DependencyProperty.Register("RestrictTo", typeof(Restrictions), typeof(ValidatingControl), new PropertyMetadata(Restrictions.UnsignedIntegersOnly));
public Restrictions RestrictTo
{
get { return (Restrictions)GetValue(RestrictToProperty); }
set { SetValue(RestrictToProperty, value); }
}
public bool Valid
{
get { return (bool)GetValue(ValidProperty); }
set { SetValue(ValidProperty, value); }
}
public static readonly DependencyProperty ValidProperty =
DependencyProperty.Register("Valid", typeof(bool), typeof(ValidatingControl), new UIPropertyMetadata(false));
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
ValidateText(RestrictTo, _textBox.Text);
}
private void ValidateText(Restrictions restrictTo, string text)
{
// validate text, update _messageText, update Valid
}
}
}