If this was a usercontrol, it'd be child's play to bind it, but that won't work in a user control.
I want the TextBlock to update when the TextBox text changes. I can set it on launch, but it doesn't change afterwards.
protected override void OnApplyTemplate()
{
_MTBGrid = GetTemplateChild("MTBGrid") as Grid;
_previewMarkdownBlock = GetTemplateChild("PreviewMarkdownBlock") as MarkdownTextBlock;
_inputTextBox = GetTemplateChild("InputTextBox") as TextBox;
_previewMarkdownBlock.Text = _inputTextBox.Text;
base.OnApplyTemplate();
}
public string Text
{
get => (String)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
I have tried adding a TextChanged event to the xaml, but it doesn't update. I'm not very experienced with this.
private void InputTB_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateMarkdownBlock(this, _previewMarkdownBlock, _inputTextBox.Text);
}
private static void UpdateMarkdownBlock(MarkdownTextBox markdownTBControl, MarkdownTextBlock markdownPreview, String newValue)
{
if (markdownPreview != null && newValue != null)
{
markdownPreview.Text = newValue;
}
}
I am working on a datagrid which enable user to select multiple rows. but when user clicks on header of row the selection get lost.
How can I disable the row selection on right click if the row is already selected.
I tried to do it via Behavior
public class DataGridRowBehavior
{
public static readonly DependencyProperty DisableSelectionOnRightClickProperty = DependencyProperty.RegisterAttached(
"DisableSelectionOnRightClick",
typeof(bool),
typeof(DataGridRowBehavior),
new UIPropertyMetadata(false, OnDisableSelectionOnRightClick));
public static bool GetDisableSelectionOnRightClick(DependencyObject dgRow)
{
return (bool)dgRow.GetValue(DisableSelectionOnRightClickProperty);
}
public static void SetDisableSelectionOnRightClick(DependencyObject dgRow, bool value)
{
dgRow.SetValue(DisableSelectionOnRightClickProperty, value);
}
public static void SetListViewFocus(DependencyObject d, bool use)
{
d.SetValue(DisableSelectionOnRightClickProperty, use);
}
public static void OnDisableSelectionOnRightClick(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGridRowHeader header = d as DataGridRowHeader;
header.MouseRightButtonUp += header_MouseRightButtonUp;
}
static void header_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var header = sender as DataGridRowHeader;
if (header.IsRowSelected)
{
if (header.ContextMenu != null)
{
header.ContextMenu.IsOpen = true;
}
e.Handled = true;
}
}
}
But this one is not working correctly as other functionality of right click is also broken. e.g Context menu. The context menu is not enabling its Application Commands.
Is there any other way to disable the selection or let the selection remain as it is if I click on any of the selected row?
You have two choices:
Either create custom DataGrid or DataGridRow and create SelectionChanging or Selection events. It's needed to prevent selection. This time, this controls have only SelectionChanged and Selected events. Next time, I think you could write code
If you do not want create custom controls you can create a Behavior. For example:
public class SuppressButtonClickBehavior : Behavior<Button>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(OnPreviewMouseLeftButtonDown),
true);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(UIElement.PreviewMouseLeftButtonDownEvent,
new RoutedEventHandler(OnPreviewMouseLeftButtonDown));
}
private void OnPreviewMouseLeftButtonDown(Object sender, RoutedEventArgs e)
{
e.Handled = true;
if (AssociatedObject.Command != null)
{
AssociatedObject.Command.Execute(AssociatedObject.CommandParameter);
}
}
}
If you want, you can make this code more flexible. But you must understand, that you can only set e.Handled to true to prevent selection.
I have a requirement that I want to make a datagridcolumn which only accepts numeric values(integer) ,when the user enter something other than numbers handle the textbox .
I tried a lot of webpages ,Iam tired of these ,I greately appreciate anybody have the helping mind.
Based on #nit suggestion, you can create your own class derived from DataGridTextColumn like this:
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(System.Windows.FrameworkElement editingElement, System.Windows.RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
try
{
Convert.ToInt32(e.Text);
}
catch
{
// Show some kind of error message if you want
// Set handled to true
e.Handled = true;
}
}
}
In the PrepareCellForEdit method you register the OnPreviewTextInput method to the editing TextBox PreviewTextInput event, where you validate for numeric values.
In xaml, you simply use it:
<DataGrid ItemsSource="{Binding SomeCollection}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding NonNumericProperty}"/>
<local:DataGridNumericColumn Binding="{Binding NumericProperty}"/>
</DataGrid.Columns>
</DataGrid>
Hope this helps
Use TryParse instead, this helps to restrict input values to integer numbers only.
/// <summary>
/// This class help to create data grid cell which only support interger numbers.
/// </summary>
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
if (edit != null) edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
private void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
int value;
if (!int.TryParse(e.Text, out value))
e.Handled = true;
}
}
If you dont want to show any validation errors and just want to block any non-numeral value then you can create the DataGridTemplateColumn and in CellEditingTemplate use the TextBox.
<DataGridTemplateColumn Width="100*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=NumericProperty}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox PreviewTextInput="TextBox_PreviewTextInput" Text="{Binding Path=NumericProperty}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
and in PreviewTextInput of the TextBox set e.Handled = true if value is other than integer:
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
try
{
Convert.ToInt32(e.Text);
}
catch
{
e.Handled = true;
}
}
I got here looking for a solution to the same problem: constraining the input into cells on a DataGrid to be numeric. But the accepted answer did not work for me. The following did:
For the DataGrid add an event handler for PreparingForCellEdit.
In that event handler, cast the EditingElement to a TextBox and add an event handler for PreviewTextInput to the TextBox.
In the PreviewTextInput event handler set e.Handled to true, if the input should not be allowed.
The above steps work if the user clicks the cell to edit. However, if the cell is not in edit mode, the PreparingForCellEdit event will not be called. To perform validation in that case:
Add an event handler to the DataGrid for PreviewTextInput.
In that event handler, safely cast e.OriginalSource to a DataGridCell (exiting, if it is not a DataGridCell), check the DataGridCell's IsEditing property, and if the cell is not editing set e.Handled to true.
The effect of the above is that the user will have to click into the cell in order to edit its contents and, as such, the PreparingForCellEdit / PreviewTextInput combination above will be invoked for all changes to the cell's contents.
Just to extend #Omribitan's answer, Here is the solution with a data Paste guard added:
public class NumericTextColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var edit = editingElement as TextBox;
edit.PreviewTextInput += Edit_PreviewTextInput;
DataObject.AddPastingHandler(edit, OnPaste);
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
private void OnPaste(object sender, DataObjectPastingEventArgs e)
{
var data = e.SourceDataObject.GetData(DataFormats.Text);
if (!IsDataValid(data)) e.CancelCommand();
}
private void Edit_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
e.Handled = !IsDataValid(e.Text);
}
bool IsDataValid(object data)
{
try
{
Convert.ToInt32(data);
return true;
}
catch
{
return false;
}
}
}
For whatever it's worth, here's how I solved it. This solution allows you to specify a variety of options when validating input, allows string formatting to be used (e.g. '$15.00' in the data grid) and more.
The null value and string formatting provided by the Binding class itself do not suffice as neither act correctly when the cell is editable so this class covers it. What this does is it uses another class that I've been using for a long time already: TextBoxInputBehavior, it has been an invaluable asset for me and it originally came from WPF – TextBox Input Behavior blog post albeit the version here seems much older (but well tested). So what I did I just transferred this existing functionality I already had on my TextBoxes to the my custom column and thus I have the same behaviour in both. Isn't that neat?
Here's the code of the custom column:
public class DataGridNumberColumn : DataGridTextColumn
{
private TextBoxInputBehavior _behavior;
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var element = base.GenerateElement(cell, dataItem);
// A clever workaround the StringFormat issue with the Binding set to the 'Binding' property. If you use StringFormat it
// will only work in edit mode if you changed the value, otherwise it will retain formatting when you enter editing.
if (!string.IsNullOrEmpty(StringFormat))
{
BindingOperations.ClearBinding(element, TextBlock.TextProperty);
BindingOperations.SetBinding(element, FrameworkElement.TagProperty, Binding);
BindingOperations.SetBinding(element,
TextBlock.TextProperty,
new Binding
{
Source = element,
Path = new PropertyPath("Tag"),
StringFormat = StringFormat
});
}
return element;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
if (!(editingElement is TextBox textBox))
return null;
var originalText = textBox.Text;
_behavior = new TextBoxInputBehavior
{
IsNumeric = true,
EmptyValue = EmptyValue,
IsInteger = IsInteger
};
_behavior.Attach(textBox);
textBox.Focus();
if (editingEventArgs is TextCompositionEventArgs compositionArgs) // User has activated editing by already typing something
{
if (compositionArgs.Text == "\b") // Backspace, it should 'clear' the cell
{
textBox.Text = EmptyValue;
textBox.SelectAll();
return originalText;
}
if (_behavior.ValidateText(compositionArgs.Text))
{
textBox.Text = compositionArgs.Text;
textBox.Select(textBox.Text.Length, 0);
return originalText;
}
}
if (!(editingEventArgs is MouseButtonEventArgs) || !PlaceCaretOnTextBox(textBox, Mouse.GetPosition(textBox)))
textBox.SelectAll();
return originalText;
}
private static bool PlaceCaretOnTextBox(TextBox textBox, Point position)
{
int characterIndexFromPoint = textBox.GetCharacterIndexFromPoint(position, false);
if (characterIndexFromPoint < 0)
return false;
textBox.Select(characterIndexFromPoint, 0);
return true;
}
protected override void CancelCellEdit(FrameworkElement editingElement, object uneditedValue)
{
UnwireTextBox();
base.CancelCellEdit(editingElement, uneditedValue);
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
UnwireTextBox();
return base.CommitCellEdit(editingElement);
}
private void UnwireTextBox() => _behavior.Detach();
public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register(
nameof(EmptyValue),
typeof(string),
typeof(DataGridNumberColumn));
public string EmptyValue
{
get => (string)GetValue(EmptyValueProperty);
set => SetValue(EmptyValueProperty, value);
}
public static readonly DependencyProperty IsIntegerProperty = DependencyProperty.Register(
nameof(IsInteger),
typeof(bool),
typeof(DataGridNumberColumn));
public bool IsInteger
{
get => (bool)GetValue(IsIntegerProperty);
set => SetValue(IsIntegerProperty, value);
}
public static readonly DependencyProperty StringFormatProperty = DependencyProperty.Register(
nameof(StringFormat),
typeof(string),
typeof(DataGridNumberColumn));
public string StringFormat
{
get => (string) GetValue(StringFormatProperty);
set => SetValue(StringFormatProperty, value);
}
}
What I did is I peeked into the source code of DataGridTextColumn and handled the TextBox creation in almost the same way plus I attached the custom behaviour to the TextBox.
Here's the code of the behavior I attached (this is a behavior you can use on any TextBox):
public class TextBoxInputBehavior : Behavior<TextBox>
{
#region DependencyProperties
public static readonly DependencyProperty RegularExpressionProperty = DependencyProperty.Register(
nameof(RegularExpression),
typeof(string),
typeof(TextBoxInputBehavior),
new FrameworkPropertyMetadata(".*"));
public string RegularExpression
{
get
{
if (IsInteger)
return #"^[0-9\-]+$";
if (IsNumeric)
return #"^[0-9.\-]+$";
return (string)GetValue(RegularExpressionProperty);
}
set { SetValue(RegularExpressionProperty, value); }
}
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register(
nameof(MaxLength),
typeof(int),
typeof(TextBoxInputBehavior),
new FrameworkPropertyMetadata(int.MinValue));
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public static readonly DependencyProperty EmptyValueProperty = DependencyProperty.Register(
nameof(EmptyValue),
typeof(string),
typeof(TextBoxInputBehavior));
public string EmptyValue
{
get { return (string)GetValue(EmptyValueProperty); }
set { SetValue(EmptyValueProperty, value); }
}
public static readonly DependencyProperty IsNumericProperty = DependencyProperty.Register(
nameof(IsNumeric),
typeof(bool),
typeof(TextBoxInputBehavior));
public bool IsNumeric
{
get { return (bool)GetValue(IsNumericProperty); }
set { SetValue(IsNumericProperty, value); }
}
public static readonly DependencyProperty IsIntegerProperty = DependencyProperty.Register(
nameof(IsInteger),
typeof(bool),
typeof(TextBoxInputBehavior));
public bool IsInteger
{
get { return (bool)GetValue(IsIntegerProperty); }
set
{
if (value)
SetValue(IsNumericProperty, true);
SetValue(IsIntegerProperty, value);
}
}
public static readonly DependencyProperty AllowSpaceProperty = DependencyProperty.Register(
nameof(AllowSpace),
typeof (bool),
typeof (TextBoxInputBehavior));
public bool AllowSpace
{
get { return (bool) GetValue(AllowSpaceProperty); }
set { SetValue(AllowSpaceProperty, value); }
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewTextInput += PreviewTextInputHandler;
AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler;
DataObject.AddPastingHandler(AssociatedObject, PastingHandler);
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject == null)
return;
AssociatedObject.PreviewTextInput -= PreviewTextInputHandler;
AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler;
DataObject.RemovePastingHandler(AssociatedObject, PastingHandler);
}
private void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
string text;
if (AssociatedObject.Text.Length < AssociatedObject.CaretIndex)
text = AssociatedObject.Text;
else
text = TreatSelectedText(out var remainingTextAfterRemoveSelection)
? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text)
: AssociatedObject.Text.Insert(AssociatedObject.CaretIndex, e.Text);
e.Handled = !ValidateText(text);
}
private void PreviewKeyDownHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
e.Handled = !AllowSpace;
if (string.IsNullOrEmpty(EmptyValue))
return;
string text = null;
// Handle the Backspace key
if (e.Key == Key.Back)
{
if (!TreatSelectedText(out text))
{
if (AssociatedObject.SelectionStart > 0)
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1);
}
}
// Handle the Delete key
else if (e.Key == Key.Delete)
{
// If text was selected, delete it
if (!TreatSelectedText(out text) && AssociatedObject.Text.Length > AssociatedObject.SelectionStart)
{
// Otherwise delete next symbol
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1);
}
}
if (text == string.Empty)
{
AssociatedObject.Text = EmptyValue;
if (e.Key == Key.Back)
AssociatedObject.SelectionStart++;
e.Handled = true;
}
}
private void PastingHandler(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(DataFormats.Text))
{
var text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
if (!ValidateText(text))
e.CancelCommand();
}
else
e.CancelCommand();
}
public bool ValidateText(string text)
{
return new Regex(RegularExpression, RegexOptions.IgnoreCase).IsMatch(text) && (MaxLength == int.MinValue || text.Length <= MaxLength);
}
/// <summary>
/// Handle text selection.
/// </summary>
/// <returns>true if the character was successfully removed; otherwise, false.</returns>
private bool TreatSelectedText(out string text)
{
text = null;
if (AssociatedObject.SelectionLength <= 0)
return false;
var length = AssociatedObject.Text.Length;
if (AssociatedObject.SelectionStart >= length)
return true;
if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length)
AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart;
text = AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
return true;
}
}
All the good credit for above Behaviour class goes to blindmeis, I merely tweaked it over time. After checking his Blog I see he has a newer version of it so you may check it out. I was very happy to find out I could use his behaviour on DataGrid as well!
This solution worked really well, you can edit the cell properly via mouse/keyboard, paste the contents properly, use any binding source update triggers, use any string formatting etc. - it just works.
Here's an example of how to use it:
<local:DataGridNumberColumn Header="Nullable Int Currency" IsInteger="True" Binding="{Binding IntegerNullable, TargetNullValue=''}" StringFormat="{}{0:C}" />
Hope this helps someone.
I went on from Omris approach
however i wanted to be able to delete the cell value after it has input in case they wanted to clear it
The way i did this was overriding the CommitCellEdit method and making the string null instead of blank. Im also using decimal? in my case
public class DataGridNumericColumn : DataGridTextColumn
{
protected override object PrepareCellForEdit(System.Windows.FrameworkElement editingElement, System.Windows.RoutedEventArgs editingEventArgs)
{
TextBox edit = editingElement as TextBox;
edit.PreviewTextInput += OnPreviewTextInput;
return base.PrepareCellForEdit(editingElement, editingEventArgs);
}
protected override bool CommitCellEdit(System.Windows.FrameworkElement editingElement)
{
TextBox tb = editingElement as TextBox;
if (string.IsNullOrEmpty(tb.Text))
tb.Text = null;
return base.CommitCellEdit(editingElement);
}
void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
try
{
Convert.ToDecimal(e.Text);
}
catch
{
// Show some kind of error message if you want
// Set handled to true
e.Handled = true;
}
}
}
Is there an easy method to prompt the user to confirm a combo box selection change and not process the change if the user selected no?
We have a combo box where changing the selection will cause loss of data. Basically the user selects a type, then they are able to enter attributes of that type. If they change the type we clear all of the attributes as they may no longer apply. The problem is that to under the selection you raise the SelectionChanged event again.
Here is a snippet:
if (e.RemovedItems.Count > 0)
{
result = MessageBox.Show("Do you wish to continue?",
"Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.No)
{
if (e.RemovedItems.Count > 0)
((ComboBox)sender).SelectedItem = e.RemovedItems[0];
else
((ComboBox)sender).SelectedItem = null;
}
}
I have two solutions, neither of which I like.
After the user selects 'No', remove the SelectionChanged event handler, change the selected item and then register the SelectionChanged event handler again. This means you have to hold onto a reference of the event handler in the class so that you can add and remove it.
Create a ProcessSelectionChanged boolean as part of the class. Always check it at the start of the event handler. Set it to false before we change the selection back and then reset it to true afterwards. This will work, but I don't like using flags to basically nullify an event handler.
Anyone have an alternative solution or an improvement on the ones I mention?
I found this good implementation.
private bool handleSelection=true;
private void ComboBox_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if (handleSelection)
{
MessageBoxResult result = MessageBox.Show
("Continue change?", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No)
{
ComboBox combo = (ComboBox)sender;
handleSelection = false;
combo.SelectedItem = e.RemovedItems[0];
return;
}
}
handleSelection = true;
}
source: http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html
Maybe create a class deriving from ComboBox, and override the OnSelectedItemChanged (Or OnSelectionChangeCommitted.)
Validating within the SelectionChanged event handler allows you to cancel your logic if the selection is invalid, but I don't know of an easy way to cancel the event or item selection.
My solution was to sub-class the WPF combo-box and add an internal handler for the SelectionChanged event. Whenever the event fires, my private internal handler raises a custom SelectionChanging event instead.
If the Cancel property is set on the corresponding SelectionChangingEventArgs, the event isn't raised and the SelectedIndex is reverted to its previous value. Otherwise a new SelectionChanged is raised that shadows the base event. Hopefully this helps!
EventArgs and handler delegate for SelectionChanging event:
public class SelectionChangingEventArgs : RoutedEventArgs
{
public bool Cancel { get; set; }
}
public delegate void
SelectionChangingEventHandler(Object sender, SelectionChangingEventArgs e);
ChangingComboBox class implementation:
public class ChangingComboBox : ComboBox
{
private int _index;
private int _lastIndex;
private bool _suppress;
public event SelectionChangingEventHandler SelectionChanging;
public new event SelectionChangedEventHandler SelectionChanged;
public ChangingComboBox()
{
_index = -1;
_lastIndex = 0;
_suppress = false;
base.SelectionChanged += InternalSelectionChanged;
}
private void InternalSelectionChanged(Object s, SelectionChangedEventArgs e)
{
var args = new SelectionChangingEventArgs();
OnSelectionChanging(args);
if(args.Cancel)
{
return;
}
OnSelectionChanged(e);
}
public new void OnSelectionChanged(SelectionChangedEventArgs e)
{
if (_suppress) return;
// The selection has changed, so _index must be updated
_index = SelectedIndex;
if (SelectionChanged != null)
{
SelectionChanged(this, e);
}
}
public void OnSelectionChanging(SelectionChangingEventArgs e)
{
if (_suppress) return;
// Recall the last SelectedIndex before raising SelectionChanging
_lastIndex = (_index >= 0) ? _index : SelectedIndex;
if(SelectionChanging == null) return;
// Invoke user event handler and revert to last
// selected index if user cancels the change
SelectionChanging(this, e);
if (e.Cancel)
{
_suppress = true;
SelectedIndex = _lastIndex;
_suppress = false;
}
}
}
In WPF dynamically set the object with
if (sender.IsMouseCaptured)
{
//perform operation
}
I do not believe using the dispatcher to post (or delay) a property update is a good solution, it is more of a workaround that is not really needed. The following solution i fully mvvm and it does not require a dispatcher.
First Bind the SelectedItem with an Explicit binding Mode. //this enables us to decide whether to Commit using the UpdateSource() method the changes to the VM or to Revert using the UpdateTarget() method in the UI.
Next, add a method to the VM that confirms if the change is allowed (This method can contain a service that prompts for user confirmation and returns a bool).
In the view code behind hook to the SelectionChanged event and update the Source (i.e., the VM) or the Target (i.e. the V) in accordance to whether the VM.ConfirmChange(...) method returned value as follows:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(e.AddedItems.Count != 0)
{
var selectedItem = e.AddedItems[0];
if (e.AddedItems[0] != _ViewModel.SelectedFormatType)
{
var comboBoxSelectedItemBinder = _TypesComboBox.GetBindingExpression(Selector.SelectedItemProperty); //_TypesComboBox is the name of the ComboBox control
if (_ViewModel.ConfirmChange(selectedItem))
{
// Update the VM.SelectedItem property if the user confirms the change.
comboBoxSelectedItemBinder.UpdateSource();
}
else
{
//otherwise update the view in accordance to the VM.SelectedItem property
comboBoxSelectedItemBinder.UpdateTarget();
}
}
}
}
This is an old question, but after struggling with the issue time and again I came up with this solution:
ComboBoxHelper.cs:
public class ComboBoxHelper
{
private readonly ComboBox _control;
public ComboBoxHelper(ComboBox control)
{
_control = control;
_control.PreviewMouseLeftButtonDown += _control_PreviewMouseLeftButtonDown; ;
_control.PreviewMouseLeftButtonUp += _control_PreviewMouseLeftButtonUp; ;
}
public Func<bool> IsEditingAllowed { get; set; }
public Func<object, bool> IsValidSelection { get; set; }
public Action<object> OnItemSelected { get; set; }
public bool CloseDropDownOnInvalidSelection { get; set; } = true;
private bool _handledMouseDown = false;
private void _control_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var isEditingAllowed = IsEditingAllowed?.Invoke() ?? true;
if (!isEditingAllowed)
{
e.Handled = true;
return;
}
_handledMouseDown = true;
}
private void _control_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (!_handledMouseDown) return;
_handledMouseDown = false;
var fe = (FrameworkElement)e.OriginalSource;
if (fe.DataContext != _control.DataContext)
{
//ASSUMPTION: Click was on an item and not the ComboBox itself (to open it)
var item = fe.DataContext;
var isValidSelection = IsValidSelection?.Invoke(item) ?? true;
if (isValidSelection)
{
OnItemSelected?.Invoke(item);
_control.IsDropDownOpen = false;
}
else if(CloseDropDownOnInvalidSelection)
{
_control.IsDropDownOpen = false;
}
e.Handled = true;
}
}
}
It can be used in a custom UserControl like this:
public class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
var helper = new ComboBoxHelper(MyComboBox); //MyComboBox is x:Name of the ComboBox in Xaml
helper.IsEditingAllowed = () => return Keyboard.Modifiers != Modifiers.Shift; //example
helper.IsValidSelection = (item) => return item.ToString() != "Invalid example.";
helper.OnItemSelected = (item) =>
{
System.Console.WriteLine(item);
};
}
}
This is independent of the SelectionChanged event, there are no side effects of the event firing more often than required. So others can safely listen to the event, e.g. to update their UI. Also avoided: "recursive" calls caused by resetting the selection from within the event handler to a valid item.
The assumptions made above regarding DataContext may not be a perfect fit for all scenarios, but can be easily adapted. A possible alternative would be to check, if the ComboBox is a visual parent of e.OriginalSource, which it isn't when an item is selected.
I have some code to set the focused property of a text box, but what i'm actually after is finding out if the text box currently has the keyboard focus, I need to determine this from my view model
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached
(
"IsFocused",
typeof(bool),
typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged)
);
public static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus();
}
}
}
And the xaml is
<TextBox Text="{Binding Path=ClientCode}" c:FocusExtension.IsFocused="{Binding IsClientCodeFocused}" />
source of code
have you seen the FocusManager? you can get/set focus using this object.
Edit
Based on the comments below, here's an example of an attached property that hooks up an event and updates the source of a binding. I'll add comments where I know you'll need to make modifications. Hopefully it will point you in the right direction
public class TextBoxHelper
{
// I excluded the generic stuff, but the property is called
// EnterUpdatesSource and it makes a TextBox update it's source
// whenever the Enter key is pressed
// Property Changed Event - You have this in your class above
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UIElement sender = obj as UIElement;
if (obj != null)
{
// In my case, the True/False value just determined a behavior,
// so toggling true/false added/removed an event.
// Since you want your events to be on at all times, you'll either
// want to have two AttachedProperties (one to tell the control
// that it should be tracking the current focused state, and
// another for binding the actual focused state), or you'll want
// to find a way to only add the EventHandler when the
// AttachedProperty is first added and not toggle it on/off as focus
// changes or add it repeatedly whenever this value is set to true
// You can use the GotFocus and LostFocus Events
if ((bool)e.NewValue == true)
{
sender.PreviewKeyDown += new KeyEventHandler(OnPreviewKeyDownUpdateSourceIfEnter);
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDownUpdateSourceIfEnter;
}
}
}
// This is the EventHandler
static void OnPreviewKeyDownUpdateSourceIfEnter(object sender, KeyEventArgs e)
{
// You won't need this
if (e.Key == Key.Enter)
{
// or this
if (GetEnterUpdatesTextSource((DependencyObject)sender))
{
// But you'll want to take this bit and modify it so it actually
// provides a value to the Source based on UIElement.IsFocused
UIElement obj = sender as UIElement;
// If you go with two AttachedProperties, this binding should
// point to the property that contains the IsFocused value
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
// I know you can specify a value for a binding source, but
// I can't remember the exact syntax for it right now
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
There might be a better way of accomplishing what you're trying to do, but if not then I hope this provides a good starting point :)
in your OnIsFocusedPropertyChanged handler, you need to get a reference to the control that it is being set on and subscribe to its FocusChanged event, where you can re-set the dependency pproperty. Make sure in your XAML you set the binding mode to TwoWay