Textbox validation: Get control of/duplicate the system validation border? - c#

I'm wanting to validate Textbox entries for an application and provide both visual and verbal feedback in the case of an error. I'd like the normal Textbox border behavior you get from wpf when using ValidationRules or INotifyDataError, then in a separate TextBlock display an error message. Standard stuff. I've got a few ways to approach this but each one is giving me problems.
ValidationRules seems like the easiest system-based approach. My problem here is binding the error message to the separate TextBlock. Most examples use content presenters or error templates, but is there no simple way to bind to a ValidationResult directly? (Question 1)
INotifyDataError being the more modern approach is nice in theory, but it seems wayyy too complicated for what I'm doing.
However, the simplest way I have found is validating directly in the property's setter in the Viewmodel. Something like this:
private string _fileName;
public string FileName
{
get {return _fileName;}
set
{
_fileName = value;
if(String.IsEmptyOrNull(value))
{
FileNameError = "File must have a name.";
}
else
{
FileNameError = null;
}
RaisePropertyChanged();
}
}
private string _fileNameError;
public string FileNameError
{
get {return _fileNameError;}
set
{
_fileNameError = value;
RaisePropertyChanged();
}
}
Then, to show an error, I can bind 2x to FileNameError: Once to display the text in the TextBlock, and the other through a converter (checking for !null) to show the red border around the TextBox.
The problem here the way WPF does the red border is much better; it appears to be its own element rather than an actual border. When I try my StringToBorderBrush converter, the border's thickness reduces the TextBox size, so things no longer line up. The double whammy is that by setting the BorderBrush to transparent, I lose the TextBox's default border and focus border which I'd like to keep.
So is there a way to either duplicate what WPF is doing with the border, or to somehow control it with a property? I would think this would be possible because something has to call it when it is shown with ValidationRules or INotifyDataError, right? (Question 2)

Related

Property in View must match identical property in ViewModel

I am creating a user control (a textbox that only accepts integers). The control has to have properties to specify max/min values and whether to allow negative values etc.). I am using MVVM, in my view I have public properties e.g.
const string EXAMPLE = "Example";
string example;
public string Example
{
get { return example; }
set
{
if (value == example) return;
example = value;
OnPropertyChanged(EXAMPLE);
}
}
These properties are in my View so that someone using the control will be able to easily set them. In my ViewModel I have an identical property, I need these properties to be bound together so that they and their backing fields always have the same value. I hate the code repetition too.
To be honest the whole approach feels wrong and usually that is a good indication that I am approaching the whole thing from the wrong direction or misunderstanding something fundamental.
I have used WPF before but this is a first attempt at a custom control.
The first thing I want to make sure is that you're truly trying to make a CustomControl and not a UserControl. I believe this question basically is the same as yours except worded differently.
A UserControl lends itself to the MVVM pattern way more readily than a CustomControl because you would have a .xaml (and .xaml.cs) file along with a .cs file to serve as the ViewModel. On the other hand, a CustomControl is never done with MVVM, as the visual appearance (view) is defined and overridable via a ControlTemplate.
Since you said you have a View and ViewModel, let's think about how you would achieve the behavior you want with your textbox. Your textbox will have to validate and reject user input outside the range of values you desire. This means your View code-behind has to have properties and logic that control the restrictions in the input values of the textbox defined in your View. You have already violated MVVM here.
When you said you have a View, that makes me think you're writing a UserControl. But your requirements (a custom behavior for textbox) suggest that you really need a CustomControl, for which you do not use MVVM.
If you agree that you need a CustomControl, here's a quick and dirty example:
public class RestrictedTextBox : TextBox
{
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(int), typeof(RestrictedTextBox), new PropertyMetadata(int.MaxValue));
public RestrictedTextBox()
{
PreviewTextInput += RestrictedTextBox_PreviewTextInput;
}
public int MaxValue
{
get
{
return (int)GetValue(MaxValueProperty);
}
set
{
SetValue(MaxValueProperty, value);
}
}
private void RestrictedTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
int inputDigits;
RestrictedTextBox box = sender as RestrictedTextBox;
if (box != null)
{
if (!e.Text.All(Char.IsDigit))
{
// Stops the text from being handled
e.Handled = true;
}
else if (int.TryParse(box.Text + e.Text, out inputDigits))
{
if (inputDigits > MaxValue)
e.Handled = true;
}
}
}
}
XAML Usage:
<local:RestrictedTextBox MaxValue="100"></local:RestrictedTextBox>
There is no MVVM in a Custom Control.
Any Control is only in the View layer. So what you need to do is expose relevant DP to a consumer that you have no knowledge of.
In the Custom Control you need to define your Control behavior, how it reacts to change in DP value and what should be available to a consumer. In the default Template you define how you want to display this Control.
The consumer may want to set or get some dp values so he'll have to bind your Custom Control'dp to a property in his ViewModel but that's up to him.

Custom control components setting position C#

I have custom control - using Win Forms, that contains four TextBoxes, all have property to turn them off or on - I just setting visible parameter on them.
I would like to change size and position of the custom control - for example, when I turn off first textbox, I would like to change position of all 3 componets below him, to get them higher.
Of course, I would like to work it with every TextBox - every TextBoxes, below TextBox I am changing position, should change position.
I cant achieve it with changing of Position of TextBox in its own property - I can ask TextBox on top of me, if its property is set to on or of, but it dont works, because I dont know order of setting property in the application.
I can change position of TextBox below me - in the property of Top textbox, but I can do that with only one TextBox below, I dont know and cant find out, if two TextBoxes below are not off and fourth TextBox should be on position of second.
I cant change it by using some variable - when I change it, other TextBoxes dont care about it and they have set their position before.
So do you have any idea how could I achieve it?
The FlowLayoutPanel is designed for exactly this kind of behavior. Place your textboxes inside a FlowLayoutPanel, and then when you set the visible property of one or more of them to false, the other textboxes will automatically move up (or over if that's how you have it set up).
If you want for some reason do it manually, just make a chain of controls.
public class CustomTextBox
{
public CustomTextBox(CustomTextBox previousSibling)
{
PreviousSibling = previousSibling;
}
public CustomTextBox PreviousSibling { get; private set; }
public CustomTextBox PreviousVisibleSibling
{
get
{
if (PreviousSibling == null)
{
return null;
}
return PreviousSibling.Visible ? PreviousSibling : PreviousSibling.PreviousVisibleSibling
}
}
}

Silverlight ChildWindow Size binding

I am trying to bind ChildWindow Height property to my viewmodel property , but I think it only reads VM value on first load and doesn't change size when VM changes & notifies about the change. In debugger I see that it goes into Height getter once, further notifications don't change ChildWindow size..
I think it should be bindable so I am wondering whether some issue exists here or I am doing some mistake?
Sounds like one time binding, but its oneway..
Height="{Binding WindowHeight,Mode=OneWay}"
Further investigation shows that when we change binding to Mode=TwoWay and add an empty setter it begins to behave as expected. But that doesn't explain the reason why OneWay binding doesn't work. Also value that is passed to setter is equal to my whole application height, not just childwindow that is obviously supposed to be smaller.
The most strange thing with this whole situation is the following:
Also this value is passed to setter
4 consecutive times everytime after a getter is called (see
count++ that is used to count that).
It is fired before dialog is actually shown, and it always goes in sequence get,set,set,set,set
Code for view model is super simple. Nowhere in code anyone is using ChildWindow Height, its only set in its xaml binding as shown above.
private int count = 0;
public int WindowHeight
{
get { return IsDefaultMode? DEFAULT_HEIGHT : SPECIAL_HEIGHT; }
set {count++; }
}
My inheriting Childwindow class contains like 5 strings of text none of which affect Height in any way.
Notification about WindowHeight is not fired by WindowHeight property (as seen in code) , its fired by Mode property. Couldve been a converter around mode but its currently implemented this way cause I am not sure a special converter with a couple of magic values for this situation is a better approach.
oks . mode setter code:
public bool IsSpecialMode
{
get { return m_IsSpecialMode; }
set
{
if (m_IsSpecialMode!= value)
{
m_IsSpecialMode= value;
NotifyPropertyChanged("IsSpecialMode");
NotifyPropertyChanged("WindowHeight");
}
}
}
If the ChildWindow, or any other object, changes the Height property then your binding will be lost. Try setting it to a TwoWay binding and set a break point in your View-model's WindowHeight property's setter. That will tell you what is setting it and whether you can have a OneWay binding.
The ChildWindow class will actually set it's own Height and Width properties. For example, the following code ensures the ChildWindow always covers the root content of your application. This allows the ChildWindow to provide the overlay or faded effect when showing it's popup:
private void UpdateOverlaySize()
{
if (((this.Overlay != null) && (Application.Current != null)) && ((Application.Current.Host != null) && (Application.Current.Host.Content != null)))
{
base.Height = Application.Current.Host.Content.ActualHeight;
base.Width = Application.Current.Host.Content.ActualWidth;
// ... other code removed
}
}
So if effect, it looks like you can't use a OneWay binding on the Height or Width properties.

My RichTextBox change backgroundcolor when Enabled is set to false

How can I set the backgroundcolor and fontcolor to be "normal" on a disabled (i.e. Enabled = false) RichTextBox?
Thanks
Windows User Interface guidelines demand that a control that is disabled appears disabled. With the obvious benefit that the user can tell that it won't make sense to keep banging the mouse on the control, trying to set the focus to it. Like all controls in the toolbox, RichTextBox implements this guideline as well. Overriding its painting behavior is not practical. Consider the ReadOnly property.
I would create a new control that inherits from RichTextBox. You could the override the BackColor property to always return something like white for example. Something similar could be done with the font color. Off the top of my head I think you could do something such as:
class CustomRichTextBox : System.Windows.Forms.RichTextBox {
public override System.Drawing.Color BackColor {
get { return System.Drawing.Color.White; }
set { base.BackColor = System.Drawing.Color.White; }
}
}
Though that may not work because you would probably have to override the OnPaint method to get around default greyed out behavior.
Another option would be to simply use the readonly property instead. ReadOnly is almost the same as enabled = false, except that you can actually still click in the text box (you just can't edit it). When it is readonly, you still have control over the normal color properties without having to override anything.
If you wanted to be even more creative, you could add a delegate to the Enter event of the RichTextBox that set the focus to some other control to prevent the user from even clicking in the box (which enabled doesn't let you do)
Emulate the property of being disabled. Implement a property that when set to false the control won't get focus or all key strokes are ignored.
Pretty bizarre in my opinion but the programmer wants what the programmer wants! ;-]

UpdateSourceTrigger=PropertyChanged and Converter

I have a simple Converter that adds a "+" symbol to a positive number that is entered in a TextBox. When the number is entered I want to initiate some action, but I don't want to wait until the TextBox loses focus: I want to update the binding immediately as the user enters the text.
The default behaviour of a TextBox is that when a user navigates away from the box, the binding source is updated (UpdateSourceTrigger=LostFocus). In this scenario, my converter works as expected, and the + is added. However, when I change it to the following, the + is never added.
<TextBox Text="{Binding Value, Converter={StaticResource PlusConverter}, UpdateSourceTrigger=PropertyChanged}" />
I can imagine that there is a good reason why my converter.Convert() method is not called. I would like to have the following behaviour:
When the users enters text, the source is updated immediately
When the TextBox loses focus, the + is added.
Now I'm looking for a nice, but especially GENERIC way of doing this (as I have a lot of these TextBoxes in my app).
So far I haven't been able to come up with a proper solution.
Agree w/Kent B, you need to post your Converter code.
I've been able to get part 1 to work with a simple converter (I'm binding a second unconverted TextBlock to show that the value is indeed getting updated).
However, if I understand your part 2, you're trying to get the TextBox's text to update with a "+" after it loses focus. This is a little trickier and I don't think you'll be able to do it with just an IConverter. If it can be done, I'd love to know the answer as well.
What you're essentially asking for is watermarked input behavior e.g. allow a user to enter some data, and have it get formatted correctly (both in the underlying DataBinding and in the UI). The quickest/dirtiest solution to this is to handle the TextBoxes' LostFocus but since you're using that all over your app, this may not be feasible.
You could also consider wrapping the TextBox in your own UserControl. If you look at WPFToolkit's implementation of a DatePicker it has similar behavior: allow the user to enter free form text, then auto-convert the value to a DateTime (if valid) and show the DateTime in a localized format.
HTH.
The other thing you might want to do, is edit the template for TextBox and move the actual PART_ContentHost to the right a bit, then have a TextBlock indicate the +/- part; i.e. change the template of the TextBox from:
Control
- Border
-- PART_ContentHost (the actual editing part)
into:
Control
- Border
-- Horizontal StackPanel
--- TextBlock (contains +/- sign, has 2px right margin)
--- PART_ContentHost (actual editable section)
Then, bind the TextBlock's content to the text, but with a converter that either writes a '+' or '-'. This way, the user can't delete the +/- part, and you don't have to worry about parsing it; this also makes it easier if you want to do something like make the negative sign red or something.
Thanks for your answers! I looked into this issue myself a bit futher and came up with the following solution (which I'm not entirely satisfied with, but it works fine)
I've created a CustomControl that adds functionality to the TextBox.
It provided an event handler for the LostFocus event
When this event occurs, my converter is called.
However, the way I resolve the Converter is not very satisfying (I take it from the Style that is associated with my TextBox). The Style has a Setter for the Text property. From that setter I can access my Converter.
Of course I could also make a "PlusTextBox", but I use different converters and I wanted a generic solution.
public class TextBoxEx : TextBox
{
public TextBoxEx()
{
AddHandler(LostFocusEvent,
new RoutedEventHandler(CallConverter), true);
}
public Type SourceType
{
get { return (Type)GetValue(SourceTypeProperty); }
set { SetValue(SourceTypeProperty, value); }
}
// Using a DependencyProperty as the backing store for SourceType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceTypeProperty =
DependencyProperty.Register("SourceType", typeof(Type), typeof(TextBoxEx), new UIPropertyMetadata(null));
private static void CallConverter(object sender, RoutedEventArgs e)
{
TextBoxEx textBoxEx = sender as TextBoxEx;
if (textBoxEx.Style == null) {
return;
}
if (textBoxEx.SourceType == null) {
}
foreach (Setter setter in textBoxEx.Style.Setters) {
if (setter.Property.ToString() == "Text") {
if (! (setter.Value is Binding) ) {
return;
}
Binding binding = setter.Value as Binding;
if (binding.Converter == null) {
return;
}
object value = binding.Converter.ConvertBack(textBoxEx.Text, textBoxEx.SourceType, binding.ConverterParameter, System.Globalization.CultureInfo.CurrentCulture);
value = binding.Converter.Convert(value, typeof(string), binding.ConverterParameter, System.Globalization.CultureInfo.CurrentCulture);
if (!(value is string)) {
return;
}
textBoxEx.Text = (string)value;
}
}
}
}
In your converter, couldn't you just check the keyboard's current focus? Something like:
TextBox focusedTextBox = Keyboard.FocusedElement as TextBox;
if (focusedTextBox == null || focusedTextBox != senderOrWhatever)
{
' this is where you'd add the +, because the textbox doesn't have focus.
}

Categories