In iOS when you are typing a password into a field the last letter of the field is displayed but then is obfuscated when you type the next character. Is there a way to duplicate this behavior in WPF?
If your usage for such a thing in a desktop app is justified, then you can do something like the following.
We had a similar requirement before and this is what I did.
I created a custom Passwordbox by deriving from TextBox and adding a new DP of type SecureString to it (pretty much the same concept as a normal PasswordBox). We do not lose any security benefits this way and can customize the visual behavior to our heart's content.
With this now we can use the Text of the TextBox as it's display-string and hold the actual password in the back-end SecureString DP and bind it to the VM.
We handle the PreviewTextInput and PreviewKeyDown events to manage all text changes in the control, including stuff like Key.Back, Key.Delete and the annoying Key.Space(which does not come through the PreviewTextInput
iOS Feel:
Couple more things to note for an exact iOS behavior.
Last character is only shown while adding new characters to the "end of the current string" (FlowDirection independent)
Editing characters in-between an existing string has no effect on mask.
Last character shown is timer-dependent (becomes an "*" after a certain period if left idle)
All copy-paste operations disabled in the control.
First 2 points can be handled pretty easily when detecting text changes, for the last one we can use a DispatcherTimer to work with the display-string accordingly.
So putting this all together we end up with:
/// <summary>
/// This class contains properties for CustomPasswordBox
/// </summary>
internal class CustomPasswordBox : TextBox {
#region Member Variables
/// <summary>
/// Dependency property to hold watermark for CustomPasswordBox
/// </summary>
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register(
"Password", typeof(SecureString), typeof(CustomPasswordBox), new UIPropertyMetadata(new SecureString()));
/// <summary>
/// Private member holding mask visibile timer
/// </summary>
private readonly DispatcherTimer _maskTimer;
#endregion
#region Constructors
/// <summary>
/// Initialises a new instance of the LifeStuffPasswordBox class.
/// </summary>
public CustomPasswordBox() {
PreviewTextInput += OnPreviewTextInput;
PreviewKeyDown += OnPreviewKeyDown;
CommandManager.AddPreviewExecutedHandler(this, PreviewExecutedHandler);
_maskTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 1) };
_maskTimer.Tick += (sender, args) => MaskAllDisplayText();
}
#endregion
#region Commands & Properties
/// <summary>
/// Gets or sets dependency Property implementation for Password
/// </summary>
public SecureString Password {
get {
return (SecureString)GetValue(PasswordProperty);
}
set {
SetValue(PasswordProperty, value);
}
}
#endregion
#region Methods
/// <summary>
/// Method to handle PreviewExecutedHandler events
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="executedRoutedEventArgs">Event Text Arguments</param>
private static void PreviewExecutedHandler(object sender, ExecutedRoutedEventArgs executedRoutedEventArgs) {
if (executedRoutedEventArgs.Command == ApplicationCommands.Copy ||
executedRoutedEventArgs.Command == ApplicationCommands.Cut ||
executedRoutedEventArgs.Command == ApplicationCommands.Paste) {
executedRoutedEventArgs.Handled = true;
}
}
/// <summary>
/// Method to handle PreviewTextInput events
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="textCompositionEventArgs">Event Text Arguments</param>
private void OnPreviewTextInput(object sender, TextCompositionEventArgs textCompositionEventArgs) {
AddToSecureString(textCompositionEventArgs.Text);
textCompositionEventArgs.Handled = true;
}
/// <summary>
/// Method to handle PreviewKeyDown events
/// </summary>
/// <param name="sender">Sender object</param>
/// <param name="keyEventArgs">Event Text Arguments</param>
private void OnPreviewKeyDown(object sender, KeyEventArgs keyEventArgs) {
Key pressedKey = keyEventArgs.Key == Key.System ? keyEventArgs.SystemKey : keyEventArgs.Key;
switch (pressedKey) {
case Key.Space:
AddToSecureString(" ");
keyEventArgs.Handled = true;
break;
case Key.Back:
case Key.Delete:
if (SelectionLength > 0) {
RemoveFromSecureString(SelectionStart, SelectionLength);
} else if (pressedKey == Key.Delete && CaretIndex < Text.Length) {
RemoveFromSecureString(CaretIndex, 1);
} else if (pressedKey == Key.Back && CaretIndex > 0) {
int caretIndex = CaretIndex;
if (CaretIndex > 0 && CaretIndex < Text.Length)
caretIndex = caretIndex - 1;
RemoveFromSecureString(CaretIndex - 1, 1);
CaretIndex = caretIndex;
}
keyEventArgs.Handled = true;
break;
}
}
/// <summary>
/// Method to add new text into SecureString and process visual output
/// </summary>
/// <param name="text">Text to be added</param>
private void AddToSecureString(string text) {
if (SelectionLength > 0) {
RemoveFromSecureString(SelectionStart, SelectionLength);
}
foreach (char c in text) {
int caretIndex = CaretIndex;
Password.InsertAt(caretIndex, c);
MaskAllDisplayText();
if (caretIndex == Text.Length) {
_maskTimer.Stop();
_maskTimer.Start();
Text = Text.Insert(caretIndex++, c.ToString());
} else {
Text = Text.Insert(caretIndex++, "*");
}
CaretIndex = caretIndex;
}
}
/// <summary>
/// Method to remove text from SecureString and process visual output
/// </summary>
/// <param name="startIndex">Start Position for Remove</param>
/// <param name="trimLength">Length of Text to be removed</param>
private void RemoveFromSecureString(int startIndex, int trimLength) {
int caretIndex = CaretIndex;
for (int i = 0; i < trimLength; ++i) {
Password.RemoveAt(startIndex);
}
Text = Text.Remove(startIndex, trimLength);
CaretIndex = caretIndex;
}
private void MaskAllDisplayText() {
_maskTimer.Stop();
int caretIndex = CaretIndex;
Text = new string('*', Text.Length);
CaretIndex = caretIndex;
}
#endregion
}
Working Sample:
Download Link
You can type something into the control and check the stored value shown below it.
In this sample, I've added a new DP of type string to just show that the control works fine. You'd obviously not want to have that DP (HiddenText) in your live-code, but I'd hope the sample helps to analyze if the class actually works :)
Related
I am trying to use validation to display validation errors on windows elements (actually text boxes), but I failed to get the text boxes that are not focused/edited to update their validation when conditions for failure changed (neither using INotifyDataErrorInfo nor IDataErrorInfo).
Let's say, TextBox1 validate to Error when TextBox2 holds a specific path. Now after changing the path in TextBox2, TextBox1 should clear its error automatically, but this just did not happen, I always hat to enter the TextBox and change its content for validation to update...
Therefore I intended to use Behaviors in order to bind them to a Validation Boolean value and let the behavior set the TextBox in the appropriate VisualState using the default Validation States (Valid, InvalidFocused, InvalidUnfocused).
This is my Behavior (currently only a PoC so no Dependency Property):
/// <summary>
/// Behavior for setting the visual style depending on a validation value
/// </summary>
public class TextBoxValidationBindingBehavior : BehaviorBase<TextBox>
{
/// <summary>
/// Setup the behavior
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged += this.AssociatedObject_TextChanged;
}
/// <summary>
/// Set visual state
/// </summary>
private void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = this.AssociatedObject as TextBox;
if (textBox.Text == "Test")
{
VisualStateManager.GoToState(textBox, "Valid", false);
}
else
{
if (textBox.Focus())
{
VisualStateManager.GoToState(textBox, "InvalidFocused", true);
}
else
{
VisualStateManager.GoToState(textBox, "InvalidUnfocused", true);
}
}
}
/// <summary>
/// Clean-up the behavior
/// </summary>
protected override void OnCleanup()
{
this.AssociatedObject.TextChanged -= this.AssociatedObject_TextChanged;
base.OnCleanup();
}
}
And the TextBox definition:
<TextBox Grid.Row = "0"
Grid.Column = "1"
Margin = "0, 2, 0, 2"
VerticalAlignment = "Stretch"
VerticalContentAlignment = "Center"
Text = "{Binding NewBookName}">
<b:Interaction.Behaviors>
<behavior:TextBoxValidationBindingBehavior />
</b:Interaction.Behaviors>
</TextBox>
Setting breakpoints I can see that the code gets called as expected. But the VisualStateManager.GoToState has absolutely no impact on the TextBox!
If I define a template for the text box and set custom VisualStates the behavior will work. However, the point was not to redefine visual states for the TextBox but rather to use the existing states just by associating a Behavior bound top a validation boolean and a message to display...
I'd really appreciate any hint!!! Also, I'd be happy to provide more information if required.
For the time being, I had to give up on just setting the Visual States 😣
I needed to create a Behavior that will when bound to the control add an ad-hoc ValidationRule. In principle:
For the custom validation to work (the point is only to get the default visual style on an error), the TextChanged event needs to disable the validation, update the source, then enable validation and re-update the source for the validation to happen
The IsBindingValid dependency property when changed will also update the source to trigger validation
All in all, this works:
/// <summary>
/// Behavior for setting the visual style depending on a validation value
/// </summary>
public class TextBoxValidationBindingBehavior : BehaviorBase<TextBox>
{
#region Internal Validation Class
/// <summary>
/// A validation rule that validates according to a dependency property binding rather than on control content
/// </summary>
private class BindingValidationRule : ValidationRule
{
#region Initialization
/// <summary>
/// Constructor
/// </summary>
/// <param name="that">Behavior holding this class</param>
public BindingValidationRule(TextBoxValidationBindingBehavior that) { this._that = that; }
#endregion
/// <summary>
/// Reference to behavior holding the object
/// </summary>
private readonly TextBoxValidationBindingBehavior _that;
/// <summary>
/// Flag indication that the next validation check is to be disabled / set to true
/// </summary>
public bool DisableValidationOnce = true;
/// <summary>
/// Validates the control
/// </summary>
/// <param name="value">Value to validate (ignored)</param>
/// <param name="cultureInfo">Culture Information</param>
/// <returns>Returns the <see cref="ValidationResult"/> of this validation check</returns>
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (this._that is { } that)
{
ValidationResult validationResult;
if (that.IsBindingValid || this.DisableValidationOnce)
validationResult = new ValidationResult(true, null);
else
validationResult = new ValidationResult(false, that.ErrorText);
// Re-enable validation
this.DisableValidationOnce = false;
// Set Error Tooltip
that.AssociatedObject.ToolTip = validationResult.IsValid ? null : new ToolTip() { Content = validationResult.ErrorContent };
// return result
return validationResult;
}
else throw new Exception($"Internal TextBoxValidationBindingBehavior error.");
}
}
#endregion
#region DepProp: IsBindingValid
public static readonly DependencyProperty IsBindingValidProperty = DependencyProperty.Register("IsBindingValid", typeof(bool), typeof(TextBoxValidationBindingBehavior), new PropertyMetadata(false, IsBindingValidProperty_PropertyChanged));
public bool IsBindingValid
{
get => (bool)this.GetValue(IsBindingValidProperty);
set => this.SetValue(IsBindingValidProperty, value);
}
private static void IsBindingValidProperty_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBoxValidationBindingBehavior _this)
{
// Avoid unnecessary notification propagation (the prop probably changed du to us updating the source property)
if (_this._isValidating) return;
// Trigger validation
if (_this.AssociatedObject is { } textBox && textBox.GetBindingExpression(TextBox.TextProperty) is { } bindingExpression)
bindingExpression.UpdateSource();
}
}
#endregion
#region DepProp: ErrorText
public static readonly DependencyProperty ErrorTextProperty = DependencyProperty.Register("ErrorText", typeof(string), typeof(TextBoxValidationBindingBehavior), new PropertyMetadata("Error"));
public string ErrorText
{
get => (string)this.GetValue(ErrorTextProperty);
set => this.SetValue(ErrorTextProperty, value);
}
#endregion
#region Private properties
/// <summary>
/// The custom validation rule to handle bound validation
/// </summary>
private BindingValidationRule _bindingValidationRule { get; set; }
/// <summary>
/// Indicate if validation already happening to avoid verbose notifications in the application
/// </summary>
private bool _isValidating;
#endregion
/// <summary>
/// Setup the behavior
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
// Set handler(s)
this.AssociatedObject.TextChanged += this.AssociatedObject_TextChanged;
// Create custom validation rule
this._bindingValidationRule = new BindingValidationRule(this);
// Add rule
if (this.AssociatedObject is { } textBox && BindingOperations.GetBinding(textBox, TextBox.TextProperty) is { } binding)
{
// We must be able to handle updating the source in order to set value bypassing validation
if (binding.UpdateSourceTrigger == UpdateSourceTrigger.PropertyChanged) throw new Exception("Cannot set UpdateSourceTrigger to PropertyChanged when using TextBoxValidationBindingBehavior");
// Add custom validation rule
binding.ValidationRules.Add(this._bindingValidationRule);
}
}
/// <summary>
/// Set visual state
/// </summary>
private void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e)
{
if (this.AssociatedObject is { } textBox && textBox.GetBindingExpression(TextBox.TextProperty) is { } bindingExpression)
{
this._isValidating = true;
// Remove validation before updating source (or validation will prevent source from updating if it errors)
this._bindingValidationRule.DisableValidationOnce = true;
// Update Source
bindingExpression.UpdateSource();
// Ensure we are not disabled (if UpdateSource did not call Validation)
this._bindingValidationRule.DisableValidationOnce = false;
// Trigger validation
bindingExpression.UpdateSource();
this._isValidating = false;
}
}
/// <summary>
/// Clean-up the behavior
/// </summary>
protected override void OnCleanup()
{
this.AssociatedObject.TextChanged -= this.AssociatedObject_TextChanged;
// Remove rule
if (this.AssociatedObject is { } textBox && BindingOperations.GetBinding(textBox, TextBox.TextProperty) is { } binding)
{
binding.ValidationRules.Remove(this._bindingValidationRule);
}
base.OnCleanup();
}
}
And the XAML code:
<TextBox Grid.Row = "0"
Grid.Column = "1"
Margin = "0, 2, 0, 2"
VerticalAlignment = "Stretch"
VerticalContentAlignment = "Center"
Text = "{Binding NewBookName}">
<b:Interaction.Behaviors>
<behavior:TextBoxValidationBindingBehavior IsBindingValid = "{Binding IsValidName}"
ErrorText = "Invalid name or a book with the same name already exists."/>
</b:Interaction.Behaviors>
</TextBox>
However, there are a few things I really don't like about this way of proceeding:
This procedure is very verbose in that it triggers potentially a lot of notification bindings every time it updates the source just to validate the content
It may not be thread-safe?!
While it is theoretically possible to use it with other validation rules, it would probably need a lot of code to get it to work
I find this quite hacky...
I hope this can help others or if one has a better idea: YOU ARE WELCOME!!! 😊
This question already has answers here:
What's the best way to pass event to ViewModel?
(3 answers)
Closed 8 years ago.
I am trying to find a simple example on how to bind some TextBox events (PreviewTextInput and PreviewKeyDown) to a Commands, however I can't find any clear example and all the exmaples I found so far enforce me to use some MVVM framework (Light toolkit, Prism, etc.), however currently I don't want to use a framework because I want to understand more deeply how the business works.
Can anyone please supply a simple example on how this can be achieved?
Is it absolutely necessary to use MVVM framework?
Thanks in advance.
The easy way is to attach a common event handler to the event in XAML, and invoke the Command in code-behind. Such an event handler could look like the following:
private void TextBox_OnTextChanged(object sender, EventArgs e)
{
var viewmodel = this.DataContext as MyViewmodel;
if (viewmodel != null)
{
viewmodel.SomeCommand.Execute();
}
}
An alternative that works without any code in the code-behind (but is a bit tricky to implement, and works only on .NET 4.5) is to implement your own MarkupExtension, such that you can code something like
<TextBox TextChanged="{myMarkupExtension:CommandBinding SomeCommand]">...</TextBox>
There are a few articles out there describing this approach, for example this one
You can inherit TextBox and implement ICommandSource. I have done the same, my implementation looked like this. You should be able to extend this to work on PreviewTextInput.
public class CommandTextBox : TextBox, ICommandSource
{
private bool _canExecute;
private EventHandler _canExecuteChanged;
/// <summary>
/// DependencyProperty for Command property.
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandTextBox), new PropertyMetadata(OnCommandChanged));
/// <summary>
/// Gets or sets the command to invoke when the enter key is pressed.
/// </summary>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
/// <summary>
/// DependencyProperty for CommandParameter property.
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandTextBox));
/// <summary>
/// Gets or sets the parameter to pass to the Command property.
/// </summary>
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
/// <summary>
/// Gets or sets a value that indicates whether the command resets the text property.
/// </summary>
public bool CommandResetsText { get; set; }
/// <summary>
/// DependencyProperty for CommandTarget property.
/// </summary>
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CommandTextBox));
/// <summary>
/// Gets or sets the element on which to raise the specified command.
/// </summary>
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
/// <summary>
/// Gets a value that becomes the return value of
/// System.Windows.UIElement.IsEnabled in derived classes.
/// </summary>
protected override bool IsEnabledCore
{
get { return base.IsEnabledCore && _canExecute; }
}
/// <summary>
/// Command dependency property change callback.
/// </summary>
/// <param name="d">Dependency Object</param>
/// <param name="e">Event Args</param>
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandTextBox tb = (CommandTextBox)d;
tb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
/// <summary>
/// If Command is defined, pressing the enter key will invoke the command;
/// Otherwise, the textbox will behave normally.
/// </summary>
/// <param name="e">Provides data about the event.</param>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter && Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
command.Execute(CommandParameter, CommandTarget);
else
Command.Execute(CommandParameter);
if (CommandResetsText)
this.Text = String.Empty;
}
}
/// <summary>
/// Add a command to the Command Property.
/// </summary>
/// <param name="command">Command</param>
private void AddCommand(ICommand command)
{
var handler = new EventHandler(CanExecuteChanged);
_canExecuteChanged = handler;
if (command != null)
command.CanExecuteChanged += _canExecuteChanged;
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (Command != null)
{
RoutedCommand command = Command as RoutedCommand;
// If a RoutedCommand.
if (command != null)
_canExecute = command.CanExecute(CommandParameter, CommandTarget);
else
_canExecute = Command.CanExecute(CommandParameter);
}
CoerceValue(UIElement.IsEnabledProperty);
}
/// <summary>
/// Add a new command to the Command Property.
/// </summary>
/// <param name="oldCommand">Old Command</param>
/// <param name="newCommand">New Command</param>
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
RemoveCommand(oldCommand);
AddCommand(newCommand);
}
/// <summary>
/// Remove a command from the Command Property.
/// </summary>
/// <param name="command">Command</param>
private void RemoveCommand(ICommand command)
{
EventHandler handler = CanExecuteChanged;
command.CanExecuteChanged -= handler;
}
}
I have extended the XamDataGrid to support DynamicColumn generation using a DependencyProperty called ColumnSource. Thus the gird now will generate columns dynamically based on a dependency property called "ColumnSource". this idea was inspired from the DevExpress WPF Grid I had used before.
Having said that, I need to mention that I am using Field (not UnBoundField) inside the extended control to generate the columns and binding them to the ViewModel objects. It has worked fine for requirement I had till now.
Now I have a situation where I have a ViewModel that needs to have dynamic properties. Obviously I have ICustomTypeDescriptor in mind.I just am curious is it possible to view data in the XamDataGrid with the following limitations:
.Net 4.0
ICustomTypeDescriptor
Use of field and not UnboundField class for column generations.
Data shown should be two way bindable,
that is change in cell data should change appropriate ViewModel
property.
I am pasting the Extended control's code here. It is very long so I will try to curtail the code responsible for other functionalities.
public class AdvancedXamDataGrid : XamDataGrid
{
#region Static Constructor
static AdvancedXamDataGrid()
{
//Dependency properties overrides if any to be done here.
DataSourceProperty.OverrideMetadata(typeof(AdvancedXamDataGrid), new FrameworkPropertyMetadata(null, DataSourcePropetyChanged));
}
#endregion
#region Dependency Properties
/// <summary>
/// Dependency proeprty for Columns List shown in the Grid Header
/// </summary>
public static readonly DependencyProperty ColumnsSourceProperty = DependencyProperty.Register("ColumnsSource", typeof(IEnumerable),
typeof(AdvancedXamDataGrid), new FrameworkPropertyMetadata(null, OnColumnsSourceChanged));
/// <summary>
/// Gets or sets the <see cref="ColumnsSource"/>.
/// This is a Dependency Property.
/// </summary>
public IEnumerable ColumnsSource
{
get { return GetValue(ColumnsSourceProperty) as IEnumerable; }
set { SetValue(ColumnsSourceProperty, value); }
}
#endregion
#region Dependency Property Property Changed Handlers (static).
/// <summary>
/// The handler is fired when the <see cref="ColumnsSource"/> is changed.
/// </summary>
/// <param name="sender">The dependency object that raises the event.</param>
/// <param name="e">The event argument</param>
private static void OnColumnsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as AdvancedXamDataGrid;
if (null != control)
{
if (null != control._fieldAdornerSettings)
control.DetachAdorner();
control._fieldAdornerSettings = new FieldAdornerSettings();
control._fieldAdornerList = new List<FieldAdorner>();
var oldValue = e.OldValue as IEnumerable;
var newValue = e.NewValue as IEnumerable;
if (BindingOperations.IsDataBound(sender, ColumnsSourceProperty))
control.ColumnsSourceChanged(oldValue, newValue);
}
}
/// <summary>
/// This handler is fired when the data source property changes.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void DataSourcePropetyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as AdvancedXamDataGrid;
if (null != control)
{
var dataSource = e.NewValue as IEnumerable;
control.DataSource = dataSource;
}
}
#endregion
#region Instance Properties and Event Handlers
/// <summary>
/// Handles when the <see cref="ColumnsSource"/> is changed.
/// </summary>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
private void ColumnsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (null != oldValue)
//I could never figure out why I need this check. But this check is requred for consistent laytout for first time load.Funny I know!
FieldLayouts.Clear(); //Clear the existing columns.
var oldColSource = oldValue as INotifyCollectionChanged;
if (null != oldColSource)
{
oldColSource.CollectionChanged -= oldColSource_CollectionChanged;
//Remove the columns first.
foreach (IGridColumn column in oldValue)
{
RemoveField(column);
}
}
var newColSource = newValue as INotifyCollectionChanged;
if (null != newColSource)
{
newColSource.CollectionChanged += oldColSource_CollectionChanged;
}
if (null != newValue)
{
var fieldLayout = new FieldLayout {IsDefault = true, Key = Convert.ToString(Guid.NewGuid())};
FieldLayouts.Add(fieldLayout);
foreach (IGridColumn col in newValue)
{
AddField(col);
}
DefaultFieldLayout = fieldLayout;
}
}
/// <summary>
/// Fires when the ColumnsSource Collection changes.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void oldColSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//Remove old Items.
foreach (IGridColumn col in e.OldItems)
{
RemoveField(col);
}
//Add new items.
foreach (IGridColumn col in e.NewItems)
{
AddField(col);
}
}
/// <summary>
/// Adds a Field to the wrapped grids FiledCollection.
/// </summary>
/// <param name="column"></param>
private void AddField(IGridColumn column)
{
if (FieldLayouts.Count > 0)
{
var fieldLayout = FieldLayouts[0];
var field = new Field {Name = column.Name, Label = column.DisplayName.ToUpper(), ToolTip = column.ToolTip};
switch (column.ColumnType)
{
// case GridColumnType.Text:
// field.DataType = typeof(string);
// break;
case GridColumnType.Boolean:
var style = new Style(typeof (XamCheckEditor));
style.Setters.Add(new Setter(XamCheckEditor.IsCheckedProperty,
new Binding()
{
Path = new PropertyPath(string.Concat("DataItem.", column.Name)),
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode = BindingMode.TwoWay
}));
field.Settings.EditorType = typeof (XamCheckEditor);
field.Settings.EditorStyle = style;
break;
}
if (column.ColumnType == GridColumnType.Combo)
{
var style = new Style(typeof (XamComboEditor));
style.Setters.Add(new Setter(XamComboEditor.ItemsSourceProperty,
new Binding() {Path = new PropertyPath(column.ItemsSource)}));
style.Setters.Add(new Setter(XamComboEditor.SelectedItemProperty,
new Binding(column.SelectedItemPropertyName) {Mode = BindingMode.TwoWay}));
style.Setters.Add(new Setter(XamComboEditor.DisplayMemberPathProperty, column.DisplayMemberPath));
style.Setters.Add(new Setter(XamComboEditor.ValuePathProperty, column.ValueMemberPath));
field.Settings.EditorType = typeof (XamComboEditor);
field.Settings.EditorStyle = style;
}
if (column.IsReadOnly)
field.Settings.AllowEdit = false;
if (!column.IsVisible)
field.Visibility = Visibility.Collapsed;
fieldLayout.Fields.Add(field);
if (!string.IsNullOrEmpty(column.TemplateKey))
_fieldAdornerList.Add(new FieldAdorner()
{
Name = column.Name,
BindToParentSource = column.BindToParent,
TemplateKey = column.TemplateKey
});
//Register to the property changed notofication.
var propertyNotifier = column as INotifyPropertyChanged;
propertyNotifier.PropertyChanged += propertyNotifier_PropertyChanged;
}
}
/// <summary>
/// Removes a field
/// </summary>
/// <param name="column"></param>
private void RemoveField(IGridColumn column)
{
if (FieldLayouts.Count > 0)
{
var fieldLayout = FieldLayouts[0];
var field = fieldLayout.Fields.FirstOrDefault(f => f.Name.Equals(column.Name));
if (null != field)
fieldLayout.Fields.Remove(field);
var propertyNotifier = column as INotifyPropertyChanged;
propertyNotifier.PropertyChanged -= propertyNotifier_PropertyChanged;
}
}
/// <summary>
/// Event handler for handling property notification.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void propertyNotifier_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var column = sender as IGridColumn;
if (null != column)
{
var fieldLayout = FieldLayouts[0];
var field = fieldLayout.Fields.FirstOrDefault(f => f.Name.Equals(column.Name));
if (e.PropertyName.Equals("IsVisible"))
{
if (field != null)
field.Visibility = column.IsVisible ? Visibility.Visible : Visibility.Collapsed;
}
if (e.PropertyName.Equals("IsReadOnly"))
{
if (field != null)
field.Settings.AllowEdit = !column.IsReadOnly;
}
}
}
#endregion
}
Here is the IGridColumn contract:
/// <summary>
/// A contract that need to be implemented by an item that needs to participate in ColumnSource binding.
/// </summary>
public interface IGridColumn : INotifyPropertyChanged
{
/// <summary>
/// Gets or sets the PropertyName to which the Column would bind.
/// </summary>
string Name { get; set; }
/// <summary>
/// Gets or sets the Display Text that will be visible in the column header.
/// </summary>
string DisplayName { get; set; }
/// <summary>
/// Gets the type of the property that gets bound to this column.
/// </summary>
GridColumnType ColumnType { get; }
/// <summary>
/// Gets or sets if the column is read-only.
/// </summary>
bool IsReadOnly { get; set; }
/// <summary>
/// Gets or sets if the column is visible.
/// </summary>
bool IsVisible { get; set; }
#region For Combo Columns
/// <summary>
/// Gets or sets the Items source of the combo editor.
/// </summary>
string ItemsSource { get; set; }
/// <summary>
/// Gets or sets the SelectedItem propertyName.
/// </summary>
string SelectedItemPropertyName { get; set; }
/// <summary>
/// Gets or sets the name of the property that be the display item of the combo.
/// </summary>
string DisplayMemberPath { get; set; }
/// <summary>
/// Gets or sets the name of the property that be the value item of the combo.
/// </summary>
string ValueMemberPath { get; set; }
/// <summary>
/// Gets or sets the tool tip on the column.
/// </summary>
string ToolTip { get; set; }
/// <summary>
/// Gets or sets the Template Key for the adorner.
/// </summary>
string TemplateKey { get; set; }
/// <summary>
/// Gets or sets if the smart tag, would be bound to the view model of the grid.
/// <remarks>
/// Note: By default it would be bound to an item of the grid.
/// </remarks>
/// </summary>
bool BindToParent { get; set; }
/// <summary>
/// Gets or sets the caption for the smart tag.
/// </summary>
string SmartTagCaption { get; set; }
#endregion
}
/// <summary>
/// An enumeration offering various types of Grid Column types.
/// </summary>
public enum GridColumnType
{
Text=0,
Boolean,
Integer,
Double,
Decimal,
Combo
} ;
I had the plan to populating the ColumnSource for the Grid and bind them to ICustomTypeDescriptor instances of ViewModels whose Dynamic property names would match with the IGridColumn Names.
I want to write either attached property or behavior for TextBox so that when focused input language changes automatically based on set property
can anyone provide me with sample code to achieve this?
Here is the code I have so far but I want to set and unset language based on textBox focus so when TextBox focus lost I want to switch to inputlanguage which was selected before this atached this class changed it
public static class InputLanguageSwitch
{
#region Dependency properties
/// <summary>
/// language to switch to when input got focus
/// </summary>
public static readonly DependencyProperty KeyboardLanguageProperty = DependencyProperty.Register("KeyboardLanguage", typeof(string), typeof(Localization), new FrameworkPropertyMetadata(null, OnKeyboardLanguageChanged));
#endregion
#region Dependency properties Get/Set methods
/// <summary>
/// Return selected keyboard language
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
public static string GetKeyboardLanguage(DependencyObject d)
{
return d.GetValue(KeyboardLanguageProperty) as string;
}
/// <summary>
/// Set input language base on keyboard
/// </summary>
/// <param name="d"></param>
/// <param name="value"></param>
public static void SetKeyboardLanguage(DependencyObject d, string value)
{
d.SetValue(KeyboardLanguageProperty, value);
}
#endregion
#region Even processing
/// <summary>
/// action to perform when input language changes
/// </summary>
/// <param name="d">Dependency object</param>
/// <param name="e">DependencyPropertyChangedEventArgs</param>
public static void OnKeyboardLanguageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is TextBoxBase)) return;
var textBox = d as TextBoxBase;
var language = FindKeyboardLanguage((string) e.NewValue);
if (string.IsNullOrEmpty((string) e.NewValue) | ReferenceEquals(language, CultureInfo.InvariantCulture))
{
//textBox.PreviewGotKeyboardFocus -= OnTextBoxPreviewGotKeyboardFocus;
d.ClearValue(InputLanguageManager.InputLanguageProperty);
d.ClearValue(InputLanguageManager.RestoreInputLanguageProperty);
}
else
{
//textBox.PreviewGotKeyboardFocus += OnTextBoxPreviewGotKeyboardFocus;
d.SetValue(InputLanguageManager.InputLanguageProperty, language);
d.SetValue(InputLanguageManager.RestoreInputLanguageProperty, true);
InputLanguageManager.Current.CurrentInputLanguage = language;
}
}
#endregion
/// <summary>
/// Find and return keyboard language if one specified as an argument
/// is installed on machine
/// </summary>
/// <param name="tag">language tag</param>
/// <returns><see cref="CultureInfo"/> for requested language></returns>
public static CultureInfo FindKeyboardLanguage(string tag)
{
CultureInfo result = CultureInfo.InvariantCulture;
if (!string.IsNullOrEmpty(tag))
{
if (InputLanguageManager.Current.AvailableInputLanguages != null)
foreach (CultureInfo c in InputLanguageManager.Current.AvailableInputLanguages)
{
if (c.IetfLanguageTag.ToUpper().StartsWith(tag.ToUpper()))
{
result = c;
}
if (c.IetfLanguageTag.ToUpper() == tag.ToUpper())
{
break; // TODO: might not be correct. Was : Exit For
}
}
}
return result;
}
}
I am using simple validations using the INotifyDataErrorInfo implementation in silverlight.
When submitting I am validating all properties to show all the errors.
I need to get the focus back to the first control with a validation error, when validation occurs.
Do we have a way to do this? Any suggestions?
Better late than never:)
I've implemented this behavior.
First you need to subscribe to your ViewModel ErrorsChanged and PropertyChanged methods. I am doing this in my constructor:
/// <summary>
/// Initializes new instance of the View class.
/// </summary>
public View(ViewModel viewModel)
{
if (viewModel == null)
throw new ArgumentNullException("viewModel");
// Initialize the control
InitializeComponent(); // exception
// Set view model to data context.
DataContext = viewModel;
viewModel.PropertyChanged += new PropertyChangedEventHandler(_ViewModelPropertyChanged);
viewModel.ErrorsChanged += new EventHandler<DataErrorsChangedEventArgs>(_ViewModelErrorsChanged);
}
Then write handlers for this events:
/// <summary>
/// If model errors has changed and model still have errors set flag to true,
/// if we dont have errors - set flag to false.
/// </summary>
/// <param name="sender">Ignored.</param>
/// <param name="e">Ignored.</param>
private void _ViewModelErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
if ((this.DataContext as INotifyDataErrorInfo).HasErrors)
_hasErrorsRecentlyChanged = true;
else
_hasErrorsRecentlyChanged = false;
}
/// <summary>
/// Iterate over view model visual childrens.
/// </summary>
/// <param name="sender">Ignored.</param>
/// <param name="e">Ignored.</param>
private void _ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if ((this.DataContext as INotifyDataErrorInfo).HasErrors)
_LoopThroughControls(this);
}
And finally add method:
/// <summary>
/// If we have error and we haven't already set focus - set focus to first control with error.
/// </summary>
/// <remarks>Recursive.</remarks>
/// <param name="parent">Parent element.</param>
private void _LoopThroughControls(UIElement parent)
{
// Check that we have error and we haven't already set focus
if (!_hasErrorsRecentlyChanged)
return;
int count = VisualTreeHelper.GetChildrenCount(parent);
// VisualTreeHelper.GetChildrenCount for TabControl will always return 0, so we need to
// do this branch of code.
if (parent.GetType().Equals(typeof(TabControl)))
{
TabControl tabContainer = ((TabControl)parent);
foreach (TabItem tabItem in tabContainer.Items)
{
if (tabItem.Content == null)
continue;
_LoopThroughControls(tabItem.Content as UIElement);
}
}
// If element has childs.
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
if (child is System.Windows.Controls.Control)
{
var control = (System.Windows.Controls.Control)child;
// If control have error - we found first control, set focus to it and
// set flag to false.
if ((bool)control.GetValue(Validation.HasErrorProperty))
{
_hasErrorsRecentlyChanged = false;
control.Focus();
return;
}
}
_LoopThroughControls(child);
}
}
}