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.
}
Related
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)
I'm developing App which communicate with RFID Reader, I have the following Label which takes it's value from the Reader (when I click on the read button on the hardware)
<Label Text="{Binding Statistics.TotalUniqueCount}" />
I want to handle and event when the text value changed, it looks like the label doesn't has such event, is there any text view control that can handle the text change event? I tried to use Entry control, but the problem that when I read the RFID tags, it gives me the first and second time correct values, but the third time it gives me wrong value , and that's happens only when I use Entry.
for example, I read 3 unique tags, it gives me first time 3, when I read more 2 tags, the number becomes 5, but when I read the third time another 3 tags, the number becomes 1.
I put Entry and Label in the same page with the same Binding, the Label shows correct values and the Entry shows wrong values.
is there any solution to handle event when this binding (Statistics.TotalUniqueCount) changes?
As was mentioned in the comments, it looks like the Statistics class must implement INotifyPropertyChanged (that's how the binding is able to update the UI when the data changes). If that's the case, you should just subscribe to that same event in your code. Wherever you have access to that Statistics variable (in code bebhind or viewmodel), just do
Statistics.PropertyChanged += (o,e)=>
{
if (e.PropertyName == "TotalUniqueCount")
{
//do something
}
}
What you can do is on whatever page/view your label is on create a new bindableproperty that is set to your binding. Then add a propertyChanged method that will get called if the text on your label changes.
public static BindableProperty TextChangedProperty = BindableProperty.Create(nameof(TextChanged), typeof(string), typeof(YourView), string.Empty, propertyChanged:OnTextChanged);
public string TextChanged
{
get { return (string)GetValue(TextChangedProperty); }
set { SetValue(TextChangedProperty, value); }
}
static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
//Do stuff
}
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.
Well, the title is not very descriptive because they do not how to name what I'm looking for. I'll try to explain it as best I can.
In the .xaml file, a control (Suppose a textbox), if you type "Text", this can not be used again property. If we write by hand, the compiler displays an error. Good already explained why I think it's now easier.
Suppose now that I have two (DependencyProperty.RegisterAttached ... ...) with the name "Propiedad_1" and "Propiedad_2" is possible, if I'm already using one, the other can not be used in the same control and vice versa?
2) Another question, within a dependency property of type string, is it possible to check if the String changed at some point (around trying to not use a variable and then comparing), I need to avoid spending a TexBox and avoid the textbox.TextChanged event.
Thanks!
EDIT
This is what I have now.
public static readonly DependencyProperty FilterSourceProperty =
DependencyProperty.RegisterAttached("FilterSource", typeof (TextBox), typeof (ListViewExtension),
new FrameworkPropertyMetadata(null, OnTextBoxTextChanged));
public static TextBox GetFilterSource(DependencyObject dObj)
{
return (TextBox) dObj.GetValue(FilterSourceProperty);
}
public static void SetFilterSource(DependencyObject dObj, TextBox value)
{
dObj.SetValue(FilterSourceProperty, value);
}
private static void OnTextBoxTextChanged(DependencyObject dObj, DependencyPropertyChangedEventArgs e)
{
var listView = dObj as ListView;
var textBox = e.NewValue as TextBox;
if ((listView == null) || (textBox == null)) return;
textBox.TextChanged += delegate(object sender, TextChangedEventArgs tcea)
{
var view = CollectionViewSource.GetDefaultView(listView.ItemsSource);
if (view == null) return;
view.Filter += item =>
{
...
...
...
...
};
};
}
In .XAML
<TextBox Name="TxtFilter"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Text="{Binding Filter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" CharacterCasing="Upper"/>
<ListView Margin="0,5,0,0"
ItemsSource="{Binding Articles}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedArticle}"
tools:ListViewExtension.FilterSource="{Binding ElementName=TxtFilter}">
In ViewModel
public string Filter
{
get { return _filter; }
set
{
if (_filter == value) return;
_filter = value;
RaisePropertyChanged();
}
}
What I need now is change the use of a "TextBox" by a string.
tools:ListViewExtension.FilterSource="{Binding Filter}"
Your question is fairly hard to understand, due to the lack of a good, minimal, complete code example as well as the poor English. The latter is understandable (though you must still do what you can to ensure you're communicating as well as possible), but the former definitely should be addressed.
Lacking those improvements, I will guess that your questions (both…for future reference, please do not post two different questions in the same post) amount to the following:
I have two attached properties, which I would like to make mutually exclusive in the XAML editor. Is this possible?
No. The behavior you're seeing applies only to a single property. The editor will complain if you try to set the same property more than once. But it's easy for it to do that, since all it has to do is check whether that property was already used in the element.
For two different properties that are supposed to be mutually exclusive, there's not any feasible way to modify the editor or compiler's behavior to check this.
As an alternative, consider implementing the two mutually-exclusive values as a single property, where that property can accept two different subclasses of a given type, and where those subclasses each represent one of the two mutually exclusive property types.
Can I optimize property updates, so that if a new value is assigned that is actually the same as the current value, no "property changed" event is raised?
Whether this is possible depends on how your code is actually written. In WPF, binding is supported through the use of DependencyProperty or INotifyPropertyChanged. Neither of these would imply a TextChanged event in an object for a Text property, which you stated is the event you don't want raised.
Note that in general, DependencyObject.SetValue() will suppress change notifications if the effective value (after coercion) has not actually changed. Note also that in most other cases, extra change notifications would not normally be a real performance issue.
Lacking a good code example, not much more advice on that second question can be offered.
If you feel that these answers don't reasonably or usefully address your questions, please improve your post so that it is more understandable.
EDIT:
With respect to the first question, and based on the code snippet you've provided (not complete), I would say that the simplest approach would be to make FilterSource have type object instead of TextBox, and then in OnTextBoxTextChanged(), check the type of the new value and handle appropriately. I.e. if it's a TextBox, do what you're doing now (mostly…see (*) below), and if it's a string instance, just configure the view's filter directly instead of putting the configuration into an event handler.
(*) note:
I see at least two areas of improvement in your OnTextBoxTextChanged() method:
There is no need to rebuild the Filter event handler just because the text's changed. Instead, you can just call Refresh() on the view. So in that approach, you would implement the event handler for the Filter event to always retrieve the TextBox.Text property value for filtering. You would subscribe to the event once, and then the event handler for TextChanged would just call Refresh().
In the string scenario, you'd use a Filter event handler that just filters using the string value, with no need to handle the (non-existent, of course) TextChanged event.
The bigger issue is that you only ever subscribe to the TextChanged event. If you only ever change the FilterSource property once, you'll never notice a problem, but it is a problem. If the property value is ever changed again, you should be unsubscribing the old event handler before subscribing a new one. If you make the change I describe above, where the TextChanged event handler is only calling Refresh(), the impact of this bug will be significantly reduced. But it's still a bug.
end of note.
As far as the second part of your question goes, I don't see a problem that needs solving. It's not clear whether you're concerned about the TextBox.Text property or the FilterSource property, but I think that neither property should generate change notifications if the newly set property value is the same as the old.
If you think differently, please provide a better code example (both minimal and complete) that illustrates clearly what actual problem occurs, along with a clear, precise explanation of what that problem is: how the code currently behaves, and how that's different from how you want it to.
Taking all of the above into account, I think your OnTextBoxTextChanged() method should look more like this:
private static void OnTextBoxTextChanged(DependencyObject dObj, DependencyPropertyChangedEventArgs e)
{
ListView listView = dObj as ListView;
if (listView == null) return;
var view = CollectionViewSource.GetDefaultView(listView.ItemsSource);
if (view == null) return;
if (e.NewValue is TextBox)
{
TextBox newValue = (TextBox)e.NewValue;
view.Filter += item =>
{
string filterString = newValue.Text;
// filter based on filterString, etc.
};
textBox.TextChanged += delegate(object sender, TextChangedEventArgs tcea)
{
view.Refresh();
};
}
else if (e.NewValue is string)
{
string filterString = (string)e.NewValue;
view.Filter += item =>
{
// filter based on filterString, etc.
};
}
else return;
}
For this to work, you'll of course have to change the type of your attached property from TextBox to object.
In the above, I did not bother to address the issue of unsubscribing from the TextChanged or Filter events. If you desire to fix that particular problem, it is relatively simple: you need to unsubscribe the old handlers from the events (for TextChanged, only if e.OldValue is TextBox is true of course).
Of course, to do this you'll need to store the old event handler delegate instances on a per-ListView object basis, e.g. in a dictionary or maybe even just having a private attached property (similar to the FilterSource property, but not visible to other code). That way, you can retrieve the delegate instances later to unsubscribe them from their events.
I have an application where bindings work almost as they should, there is just one problem:
Some GUI elements update the underlying data type "on exit" i.e. when focusing on something else. However, this doesn't happend when I click "execute" or "save" (saving settings). So if the last setting that was set was a textbox and the user didn't click somewhere else, the updated setting value is not included into the execution / setting save.
Is there a way to do this manually at execute/save?
Why doesn't my clicking on execute/save work as a focus change? Maybe it does but it doesn't happend until after the event for the button is run?
You can use this to explicitly update the binding for a specific textbox element or the focused textbox element in your execute/save method and still use LostFocus for the UpdateSourceTrigger property:
public static void UpdateBinding()
{
UpdateBinding(Keyboard.FocusedElement as TextBox);
}
public static void UpdateBinding(TextBox element)
{
if (element != null)
{
var binding = element.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
}
}
Try using UpdateSourceTrigger="PropertyChanged" on your textbox binding
EDIT
or use UpdateSourceTrigger="Explicit" and call the update method in your bindings when handling your button click