I am having a bit of difficulty in a CRUD form. We have a button which saves the form and has the IsDefault flag set to true, so that the user can press enter at any point to save the form.
The problem is that when the user is typing in a textbox and hits the enter key the source of the textbox binding isn't updated. I know that this is because the default UpdateSourceTrigger functionality for textboxes is LostFocus, which I've used to overcome the problem is some cases, but this actually causes more problems in other cases.
For standard string fields this is fine, however for things like doubles and ints the validation occurs on property change, and so stop the user from typing say 1.5 into a textbox that is bound to a double source (they can type 1, but the validation stops the decimal, they could type 15 then move the cursor back and press . though).
Is there a better way to approach this? I looked at ways to refresh all bindings in the window in code which came up with firing a PropertyChanged event with string.empty however this only refreshes the target, not the source.
My standard solution when I don't want to set the UpdateSourceTrigger=PropertyChanged on the bindings is to use a custom AttachedProperty that when set to true will will update the source of the binding when Enter is pressed.
Here's a copy of my Attached Property
// When set to True, Enter Key will update Source
#region EnterUpdatesTextSource DependencyProperty
// Property to determine if the Enter key should update the source. Default is False
public static readonly DependencyProperty EnterUpdatesTextSourceProperty =
DependencyProperty.RegisterAttached("EnterUpdatesTextSource", typeof (bool),
typeof (TextBoxHelper),
new PropertyMetadata(false, EnterUpdatesTextSourcePropertyChanged));
// Get
public static bool GetEnterUpdatesTextSource(DependencyObject obj)
{
return (bool) obj.GetValue(EnterUpdatesTextSourceProperty);
}
// Set
public static void SetEnterUpdatesTextSource(DependencyObject obj, bool value)
{
obj.SetValue(EnterUpdatesTextSourceProperty, value);
}
// Changed Event - Attach PreviewKeyDown handler
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (obj != null)
{
if ((bool) e.NewValue)
{
sender.PreviewKeyDown += OnPreviewKeyDownUpdateSourceIfEnter;
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDownUpdateSourceIfEnter;
}
}
}
// If key being pressed is the Enter key, and EnterUpdatesTextSource is set to true, then update source for Text property
private static void OnPreviewKeyDownUpdateSourceIfEnter(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (GetEnterUpdatesTextSource((DependencyObject) sender))
{
var obj = sender as UIElement;
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
#endregion //EnterUpdatesTextSource DependencyProperty
And it's used like this:
<TextBox Text="{Binding SomeText}" local:EnterUpdatesTextSource="True" />
You can update binding source by using the following code:
textBox1.GetBindingExpression(TextBox.TextProperty).UpdateSource();
Related
I have a textbox in xaml (well, several textboxes) that are behaving improperly. When I set my focus to the textbox (with or without typing anything), after some time that particular textbox loses its focus automatically. It happens fast for some textboxes whereas slow for some but occurs in all. Its killing me for last 3 days but couldn't find anything. Its just a normal textbox. If anyone has any idea or possibilities behind this, please mention it.
I have faced this issue too when I worked with complex GUI with a lot of lists, master-details, etc. Frankly, I didn't figure out what was the reason for this issue, but sometime focus just was lost during the typing.
I fixed this problem with this behavior:
public class TextBoxBehaviors
{
public static bool GetEnforceFocus(DependencyObject obj)
{
return (bool)obj.GetValue(EnforceFocusProperty);
}
public static void SetEnforceFocus(DependencyObject obj, bool value)
{
obj.SetValue(EnforceFocusProperty, value);
}
// Using a DependencyProperty as the backing store for EnforceFocus. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnforceFocusProperty =
DependencyProperty.RegisterAttached("EnforceFocus", typeof(bool), typeof(TextBoxBehaviors), new PropertyMetadata(false,
(o, e) =>
{
bool newValue = (bool)e.NewValue;
if (!newValue) return;
TextBox tb = o as TextBox;
if (tb == null)
{
MessageBox.Show("Target object should be typeof TextBox only. Execution has been seased", "TextBoxBehaviors warning",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
tb.TextChanged += OnTextChanged;
}));
private static void OnTextChanged(object o, TextChangedEventArgs e)
{
TextBox tb = o as TextBox;
tb.Focus();
/* You have to place your caret at the end of your text manually, because each focus repalce your caret at the beging of text.*/
tb.CaretIndex = tb.Text.Length;
}
}
Usage in XAML:
<TextBox x:Name="txtPresenter"
behaviors:TextBoxBehaviors.EnforceFocus="True"
Text="{Binding Path=MyPath, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" />
Scenario
I have a custom combo box where i have a label in the Combobox selection box. I need to change the label as I noted in the second image. But I want to do it only when I select items by selecting the check box. I can select multiple items, so the label should be updated as a comma separated value of selected items. If there is not enough space to display the full text of the label there should be "..." symbol to indicate that there are more items selected in the combo box.
I created a custom Label by inheriting the text Box control where I do all the changes in the callback event of a Dependency property. (Check custom Text Box code)
Now the problem is that the callback event in the custom Text box control is not firing when I change the bounded property in the View model (I am doing this by adding values to the observable collection in the code behind in check box on Check event. Please Check check box event code).
I can see that first time when I load default data in the view model the line is hit by the break point in the "Getter" part of "SelectedFilterResources" . But I never get a hit in the Setter part of the property.
Custom Text Box
The custom text box has the "CaptionCollectionChanged" callback event. It is the place where I have all logic to achieve my scenario. "Resources item" here is a type of Model.
public class ResourceSelectionBoxLable : TextBox
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
IsReadOnly = true;
}
public static List<ResourceItem> LocalFilterdResources = new List<ResourceItem>();
#region Dependancy Properties
public static readonly DependencyProperty FilterdResourcesProperty =
DependencyProperty.Register("SelectedFilterdResources",
typeof (ObservableCollection<ResourceItem>),
typeof (ResourceSelectionBoxLable),
new PropertyMetadata(new ObservableCollection<ResourceItem>(),
CaptionCollectionChanged));
public ObservableCollection<ResourceItem> SelectedFilterdResources
{
get
{
return
(ObservableCollection<ResourceItem>) GetValue(FilterdResourcesProperty);
}
set
{
SetValue(FilterdResourcesProperty, value);
LocalFilterdResources = new List<ResourceItem>(SelectedFilterdResources);
}
}
#endregion
private static void CaptionCollectionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var resourceSelectionBoxLable = d as ResourceSelectionBoxLable;
if (resourceSelectionBoxLable != null)
{
if (LocalFilterdResources.Count <= 0)
{
resourceSelectionBoxLable.Text = "Resources"
}
else
{
var actualwidthOflable = resourceSelectionBoxLable.ActualWidth;
var newValue = e.NewValue as string;
//Get the Wdith of the Text in Lable
TextBlock txtMeasure = new TextBlock();
txtMeasure.FontSize = resourceSelectionBoxLable.FontSize;
txtMeasure.Text = newValue;
double textwidth = txtMeasure.ActualWidth;
//True if Text reach the Limit
if (textwidth > actualwidthOflable)
{
var appendedString = string.Join(", ",
LocalFilterdResources.Select(item => item.ResourceCaption)
.ToArray());
resourceSelectionBoxLable.Text = appendedString;
}
else
{
if (LocalFilterdResources != null)
{
var morestring = string.Join(", ",
(LocalFilterdResources as IEnumerable<ResourceItem>).Select(item => item.ResourceCaption)
.ToArray());
var subsring = morestring.Substring(0, Convert.ToInt32(actualwidthOflable) - 4);
resourceSelectionBoxLable.Text = subsring + "...";
}
}
}
}
}
}
Custom Combo Box.
This is the control where I use the above custom label. This is also a custom control so most of the properties and styles in this controls are custom made. "DPItemSlectionBoxTemplate" is a dependency property where I enable the Selection Box of the combo box by adding an attached property to the control template. This control works fine, because I use this control in other places in my system for different purposes.
<styles:CommonMultiComboBox
x:Name="Resourcescmb" IsEnabled="{Binding IsResourceComboEnable,Mode=TwoWay}"
IsTabStop="False"
>
<styles:CommonMultiComboBox.ItemDataTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelect, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Click="CheckBox_Click"
Content="{Binding ResourceCaption}"
Style="{StaticResource CommonCheckBoxStyle}"
Tag ="{Binding}"
Checked="Resource_ToggleButton_OnChecked" />
</DataTemplate>
</styles:CommonMultiComboBox.ItemDataTemplate>
<styles:CommonMultiComboBox.DPItemSlectionBoxTemplate>
<DataTemplate>
<filtersTemplate:ResourceSelectionBoxLable
Padding="0"
Height="15"
FontSize="10"
SelectedFilterdResources="{Binding DataContext.FilterdResources,ElementName=root ,Mode=TwoWay}" />
</DataTemplate>
</styles:CommonMultiComboBox.DPItemSlectionBoxTemplate>
</styles:CommonMultiComboBox>
ViewModel
private ObservableCollection<ResourceItem> _resourceItems;
public ObservableCollection<ResourceItem> FilterdResources
{
get { return _resourceItems; }
set
{
SetOnChanged(value, ref _resourceItems, "FilterdResources");
}
}
Constructor of View Model
FilterdResources=new ObservableCollection<ResourceItem>();
"SetOnChanged" is a method in the View Model base class where we have the INotifyPropertichanged implementation.
Check Box Event
private void Resource_ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
var senderControl = sender as CheckBox;
if(senderControl==null)
return;
var selectedContent=senderControl.Tag as ResourceItem;
if (selectedContent != null)
{
ViewModel.FilterdResources.Add(selectedContent);
}
}
I can access the View Model from the Code behind through the View Model Property.
Why is the call back event not notified when I change bounded values? Am i missing something here? Dependency properties are supposed to work for two way bindings aren't they? Could any one please help me on this?
Thanks in advance.
Looks like your issue is that you're expecting the CaptionCollectionChanged event to fire when the bound collection is changed (i.e. items added or removed). When in fact this event will fire only when you're changing an instance of the bound object.
What you need to do here is to subscribe to ObservableCollection's CollectionChanged event in the setter or change callback (which you already have - CaptionCollectionChanged) of your dependency property.
public static readonly DependencyProperty FilterdResourcesProperty =
DependencyProperty.Register("SelectedFilterdResources",
typeof (ObservableCollection<ResourceItem>),
typeof (ResourceSelectionBoxLable),
new PropertyMetadata(new ObservableCollection<ResourceItem>(),
CaptionCollectionChanged));
private static void CaptionCollectionChanged(DependencyObject d,
DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
var sender = d as ResourceSelectionBoxLable;
if (sender != null)
{
collection.CollectionChanged += sender.BoundItems_CollectionChanged;
}
}
}
private void BoundItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Do your control logic here.
}
Don't forget to add cleanup logic - unsubscribe from collection change when collection instance is changed and so on.
I'm trying to setting the focus for a certain UIElement dependency object from viewmodel and to do that I have written an Attached Dependency Property as below:
#region IsFocusedProperty
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(AttachedProperties),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(OnIsFocusedChanged)));
private static void OnIsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as UIElement;
var oldValue = (bool)e.OldValue;
var newValue = (bool)e.NewValue;
if (oldValue != newValue && !obj.IsFocused)
{
obj.Focus();
}
}
public static void SetIsFoused(UIElement element, bool value)
{
element.SetValue(IsFocusedProperty, value);
}
public static bool GetIsFocused(UIElement element)
{
return (bool)element.GetValue(IsFocusedProperty);
}
#endregion
I should say it works and sends the focus to the specified element unless when I move the mouse cursor over each element in the view (including the specified element on which the focus is) no mouse event fires. I'm saying this because I've written triggers for IsMouseOverProperty of the elements but nothings happen when the focus is sent to the specified element in that way.
I should also say that the triggers works fine again when I click anywhere on the view. I really don't know what the problem is? please share your ideas. any idea would be appreciated. thanks in advance.
Edit:
I've figured it out that the element receives the keyboard focus but apparently not the logical focus because when I move the cursor to the previous element, it still fires MouseMove event.
I'm still not sure what the problem is?
Update to:
if (oldValue != newValue && !obj.IsFocused)
{
Dispatcher.BeginInvoke(DispatcherPriority.Input,
new Action(delegate()
{
obj.Focus();
Keyboard.Focus(obj);
})
);
}
I have the following TextBox:
<TextBox Text="{Binding SearchString,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
Bound to the following property:
private string _searchString;
public string SearchString
{
get
{
return _searchString;
}
set
{
value = Regex.Replace(value, "[^0-9]", string.Empty);
_searchString = value;
DoNotifyPropertyChanged("SearchString");
}
}
The class inherits from a base class that implements INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void DoNotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
All I want is a quick and dirty way to disallow non-numerical characters for an integer-only text box (I know it's not complete, just for demonstration). I don't want a mere notification that there is illegal text or anything, I want to discard right away all characters on input that are disallowed.
However, the TextBox is behaving weirdly. I can still enter any text I want, it will display as entered, e.g. "1aaa". Even though the property has been properly cleaned to "1" in this example, the Textbox still shows "1aaa". Only when I enter an actual digit that would cause _searchString to change does it also update the displayed text, for example when I have "1aaa2" it will correctly update to "12". What's the matter here?
This sounds like view-specific logic, so I see no reason not to use code-behind the view to control it. Personally I would implement this kind of behavior with a PreviewKeyDown on the TextBox that discards any non-numeric characters.
It probably wouldn't hurt to have something generic that you could reuse, such as a custom NumbersOnlyTextBox control, or an AttachedProperty you could attach to your TextBox to specify that it only allows numbers.
In fact, I remember creating an attached property that allows you to specify a regex for a textbox, and it will limit character entry to just that regex. I haven't used it in a while, so you'll probably want to test it or possible update it, but here's the code.
// When set to a Regex, the TextBox will only accept characters that match the RegEx
#region AllowedCharactersRegex Property
/// <summary>
/// Lets you enter a RegexPattern of what characters are allowed as input in a TextBox
/// </summary>
public static readonly DependencyProperty AllowedCharactersRegexProperty =
DependencyProperty.RegisterAttached("AllowedCharactersRegex",
typeof(string), typeof(TextBoxProperties),
new UIPropertyMetadata(null, AllowedCharactersRegexChanged));
// Get
public static string GetAllowedCharactersRegex(DependencyObject obj)
{
return (string)obj.GetValue(AllowedCharactersRegexProperty);
}
// Set
public static void SetAllowedCharactersRegex(DependencyObject obj, string value)
{
obj.SetValue(AllowedCharactersRegexProperty, value);
}
// Events
public static void AllowedCharactersRegexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var tb = obj as TextBox;
if (tb != null)
{
if (e.NewValue != null)
{
tb.PreviewTextInput += Textbox_PreviewTextChanged;
DataObject.AddPastingHandler(tb, TextBox_OnPaste);
}
else
{
tb.PreviewTextInput -= Textbox_PreviewTextChanged;
DataObject.RemovePastingHandler(tb, TextBox_OnPaste);
}
}
}
public static void TextBox_OnPaste(object sender, DataObjectPastingEventArgs e)
{
var tb = sender as TextBox;
bool isText = e.SourceDataObject.GetDataPresent(DataFormats.Text, true);
if (!isText) return;
var newText = e.SourceDataObject.GetData(DataFormats.Text) as string;
string re = GetAllowedCharactersRegex(tb);
re = "[^" + re + "]";
if (Regex.IsMatch(newText.Trim(), re, RegexOptions.IgnoreCase))
{
e.CancelCommand();
}
}
public static void Textbox_PreviewTextChanged(object sender, TextCompositionEventArgs e)
{
var tb = sender as TextBox;
if (tb != null)
{
string re = GetAllowedCharactersRegex(tb);
re = "[^" + re + "]";
if (Regex.IsMatch(e.Text, re, RegexOptions.IgnoreCase))
{
e.Handled = true;
}
}
}
#endregion // AllowedCharactersRegex Property
It would be used like this:
<TextBox Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
local:TextBoxHelpers.AllowedCharactersRegex="[0-9]" />
But as to why it won't update the UI. The UI knows the value hasn't actually changed, so doesn't bother re-evaluating the binding when it receives the PropertyChange notification.
To get around that, you could try temporarily setting the value to something else before setting it to the regex value, and raising a PropertyChange notification so the UI re-evaluates the bindings, but honestly that isn't really an ideal solution.
private string _searchString;
public string SearchString
{
get
{
return _searchString;
}
set
{
value = Regex.Replace(value, "[^0-9]", string.Empty);
// If regex value is the same as the existing value,
// change value to null to force bindings to re-evaluate
if (_searchString == value)
{
_searchString = null;
DoNotifyPropertyChanged("SearchString");
}
_searchString = value;
DoNotifyPropertyChanged("SearchString");
}
}
I would guess that this has something to do with WPF's built-in infinite-loop prevention logic. As written, your logic would inform WPF that the property has changed each and every time that "Set" is called. When WPF is notified that the property has changed, it will update the control. When the control updates, it will (according to your binding) call the "Set" property again. ad infinitum. WPF was designed to detect these kinds of loops and prevent them to some degree - that's probably the trap you've ended up in.
I don't know exactly how this logic works, but I think Rachel's answer is going to get you the best results. In general, the ViewModel (what you are binding to) should be a reflection of the View, bad input and all. The ViewModel should be able to validate the input (not knowing where it came from or how it was entered) and prevent bad input from propogating to the Model (by trasitioning to an "error state", for example).
What you are trying to do is control what the user is inputting, which is probably better left to the View logic.
Why don't you look at
BindingOperations.GetBindingExpressionBase( _textBoxName, TextBox.TextProperty).UpdateTarget();
updating your XAML
<TextBox x:Name="_textBoxName" Text="{Binding SearchString,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
This forces an update from source to target, you're using a DependencyProperty and your control won't update because it knows the value while sending to the binding source.
MSDN: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingexpressionbase.updatetarget.aspx
I am trying to set the initial focus to a control in a Silverlight form. I am trying to use attached properties so the focus can be specified in the XAML file. I suspect that the focus is being set before the control is ready to accept focus. Can anyone verify this or suggest how this technique might be made to work?
Here is my XAML code for the TextBox
<TextBox x:Name="SearchCriteria" MinWidth="200" Margin ="2,2,6,2" local:AttachedProperties.InitialFocus="True"></TextBox>
The property is defined in AttachedProperties.cs:
public static DependencyProperty InitialFocusProperty =
DependencyProperty.RegisterAttached("InitialFocus", typeof(bool), typeof(AttachedProperties), null);
public static void SetInitialFocus(UIElement element, bool value)
{
Control c = element as Control;
if (c != null && value)
c.Focus();
}
public static bool GetInitialFocus(UIElement element)
{
return false;
}
When I put a breakpoint in the SetInitialFocus method, it does fire and the control is indeed the desired TextBox and it does call Focus.
I know other people have created behaviors and such to accomplish this task, but I am wondering why this won't work.
You're right, the Control isn't ready to recieve focus because it hasn't finished loading yet. You can add this to make it work.
public static void SetInitialFocus(UIElement element, bool value)
{
Control c = element as Control;
if (c != null && value)
{
RoutedEventHandler loadedEventHandler = null;
loadedEventHandler = new RoutedEventHandler(delegate
{
// This could also be added in the Loaded event of the MainPage
HtmlPage.Plugin.Focus();
c.Loaded -= loadedEventHandler;
c.Focus();
});
c.Loaded += loadedEventHandler;
}
}
(In some cases, you may need to call ApplyTemplate as well according to this link)