I am trying to make a very easy UserControl that has a path that you can type in a textbox or that you can find by clicking a browse button.
I tried to do this with a dependency property but this doesn't work completely when binding to it.
Here my xaml:
<UserControl x:Class="PathSelector.PathSelector"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PathSelector">
<DockPanel Height="28">
<Button DockPanel.Dock="Right" Padding="5" Margin="5 0 0 0"
FontWeight="Bold"
Content="..."
Click="BrowseButton_Click" />
<Grid>
<TextBox
HorizontalAlignment="Stretch" VerticalAlignment="Center"
x:Name="SelectedPathTxtBox"
LostKeyboardFocus="SelectedPathTxtBox_LostKeyboardFocus" />
</Grid>
</DockPanel>
</UserControl>
And this is the codebehind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace PathSelector
{
/// <summary>
/// A simple input for path, with browse button
/// </summary>
public partial class PathSelector : UserControl
{
public PathSelector()
{
InitializeComponent();
}
private void BrowseButton_Click(object sender, RoutedEventArgs e)
{
System.Windows.Forms.OpenFileDialog fileDialog = new System.Windows.Forms.OpenFileDialog();
fileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
if (fileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
SelectedPathTxtBox.Text = fileDialog.FileName;
}
}
#region Dependency Properties
public string SelectedPath
{
get { return (string)GetValue(SelectedPathProperty); }
set { SetValue(SelectedPathProperty, value); }
}
public static readonly DependencyProperty SelectedPathProperty =
DependencyProperty.Register(
"SelectedPath",
typeof(string),
typeof(PathSelector),
new FrameworkPropertyMetadata(new PropertyChangedCallback(SelectedPathChanged))
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
private static void SelectedPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Changed!");
// How to update the values here??
}
#endregion
private void SelectedPathTxtBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
SelectedPath = SelectedPathTxtBox.Text;
}
}
}
I want to use this UserControl like this later:
<pathselector:PathSelector
SelectedPath="{Binding PathToSomeFile}"/>
"PathToSomeFile" is a string variable in the ViewModel that should be updated in both directions.
How can I achieve this? What am I missing?
Thanks a lot!
Modify SelectedPathChanged as below:
private static void SelectedPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((PathSelector)d).SelectedPathTxtBox.Text = e.NewValue.ToString();
MessageBox.Show("Changed!");
}
You should bind TextBox Text to your custom DP which will automatically update its source property.
<TextBox HorizontalAlignment="Stretch" VerticalAlignment="Center"
x:Name="SelectedPathTxtBox"
Text="{Binding SelectedPath, RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=UserControl}}"/>
Also you don't need to handle LostFocus, since Text default UpdateSourceTrigger value is LostFocus. It will update the binding property SelectedPath on lost focus.
And since SelectedPath, default UpdateSourceTrigger value is PropertyChanged, it will update PathToSomeFile whenever property changes.
If you just miss the both direction part, you can use:
<pathselector:PathSelector SelectedPath="{Binding PathToSomeFile, Mode=TwoWay}" />
More info here:
MSDN Binding.Mode Property
Related
I wrote a customcontrol. It is a textbox with a button which opens a OpenFileDialog.
The Text property of the TextBox is bound to my dependency property "FileName". And if the user selects a file via the OpenFileDialog, i set the result to this property.
The TextBox gets the right value through binding.
But now my problem. For my view I'm using a ViewModel. So I have a Binding to my DependencyProperty "FileName" to the property in my ViewModel.
After changing the "FileName" property (changes direct to the textbox or selecting a file via the dialog), the viewmodel property doesn't update.
CustomControl.xaml.cs
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
namespace WpfApplication1.CustomControl
{
/// <summary>
/// Interaction logic for FileSelectorTextBox.xaml
/// </summary>
public partial class FileSelectorTextBox
: UserControl, INotifyPropertyChanged
{
public FileSelectorTextBox()
{
InitializeComponent();
DataContext = this;
}
#region FileName dependency property
public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register(
"FileName",
typeof(string),
typeof(FileSelectorTextBox),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnFileNamePropertyChanged),
new CoerceValueCallback(OnCoerceFileNameProperty)));
public string FileName
{
get { return (string)GetValue(FileNameProperty); }
set { /*SetValue(FileNameProperty, value);*/ CoerceFileName(value); }
}
private bool _shouldCoerceFileName;
private string _coercedFileName;
private object _lastBaseValueFromCoercionCallback;
private object _lastOldValueFromPropertyChangedCallback;
private object _lastNewValueFromPropertyChangedCallback;
private object _fileNameLocalValue;
private ValueSource _fileNameValueSource;
private static void OnFileNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FileSelectorTextBox)
{
(d as FileSelectorTextBox).OnFileNamePropertyChanged(e);
}
}
private void OnFileNamePropertyChanged(DependencyPropertyChangedEventArgs e)
{
LastNewValueFromPropertyChangedCallback = e.NewValue;
LastOldValueFromPropertyChangedCallback = e.OldValue;
FileNameValueSource = DependencyPropertyHelper.GetValueSource(this, FileNameProperty);
FileNameLocalValue = this.ReadLocalValue(FileNameProperty);
}
private static object OnCoerceFileNameProperty(DependencyObject d, object baseValue)
{
if (d is FileSelectorTextBox)
{
return (d as FileSelectorTextBox).OnCoerceFileNameProperty(baseValue);
}
else
{
return baseValue;
}
}
private object OnCoerceFileNameProperty(object baseValue)
{
LastBaseValueFromCoercionCallback = baseValue;
return _shouldCoerceFileName ? _coercedFileName : baseValue;
}
internal void CoerceFileName(string fileName)
{
_shouldCoerceFileName = true;
_coercedFileName = fileName;
CoerceValue(FileNameProperty);
_shouldCoerceFileName = false;
}
#endregion FileName dependency property
#region Public Properties
public ValueSource FileNameValueSource
{
get { return _fileNameValueSource; }
private set
{
_fileNameValueSource = value;
OnPropertyChanged("FileNameValueSource");
}
}
public object FileNameLocalValue
{
get { return _fileNameLocalValue; }
set
{
_fileNameLocalValue = value;
OnPropertyChanged("FileNameLocalValue");
}
}
public object LastBaseValueFromCoercionCallback
{
get { return _lastBaseValueFromCoercionCallback; }
set
{
_lastBaseValueFromCoercionCallback = value;
OnPropertyChanged("LastBaseValueFromCoercionCallback");
}
}
public object LastNewValueFromPropertyChangedCallback
{
get { return _lastNewValueFromPropertyChangedCallback; }
set
{
_lastNewValueFromPropertyChangedCallback = value;
OnPropertyChanged("LastNewValueFromPropertyChangedCallback");
}
}
public object LastOldValueFromPropertyChangedCallback
{
get { return _lastOldValueFromPropertyChangedCallback; }
set
{
_lastOldValueFromPropertyChangedCallback = value;
OnPropertyChanged("LastOldValueFromPropertyChangedCallback");
}
}
#endregion FileName dependency property
private void btnBrowse_Click(object sender, RoutedEventArgs e)
{
FileDialog dlg = null;
dlg = new OpenFileDialog();
bool? result = dlg.ShowDialog();
if (result == true)
{
FileName = dlg.FileName;
}
txtFileName.Focus();
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged
}
}
CustomControl.xaml
<UserControl x:Class="WpfApplication1.CustomControl.FileSelectorTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="23" d:DesignWidth="300">
<Border BorderBrush="#FF919191"
BorderThickness="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="80" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<TextBox Name="txtFileName"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Grid.Column="0"
Text="{Binding FileName}" />
<Button Name="btnBrowse"
Click="btnBrowse_Click"
HorizontalContentAlignment="Center"
ToolTip="Datei auswählen"
Margin="1,0,0,0"
Width="29"
Padding="1"
Grid.Column="1">
<Image Source="../Resources/viewmag.png"
Width="15"
Height="15" />
</Button>
</Grid>
</Border>
</UserControl>
Using in a view:
<Window x:Class="WpfApplication1.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
xmlns:controls="clr-namespace:WpfApplication1.CustomControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="10" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding Files}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="File name" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<controls:FileSelectorTextBox FileName="{Binding .}" Height="30" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<ListBox ItemsSource="{Binding Files}" Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And the ViewModel:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
internal class MainViewModel
: INotifyPropertyChanged
{
public MainViewModel()
{
Files = new ObservableCollection<string> { "test1.txt", "test2.txt", "test3.txt", "test4.txt" };
}
#region Properties
private ObservableCollection<string> _files;
public ObservableCollection<string> Files
{
get { return _files; }
set
{
_files = value;
OnPropertyChanged("Files");
}
}
#endregion Properties
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged Members
}
}
Is there any wrong using of the dependency property?
Note: The problem only occurs in DataGrid.
You need to set binding Mode to TwoWay, because by default binding works one way, i.e. loading changes from the view model, but not updating it back.
<controls:FileSelectorTextBox FileName="{Binding FileName, Mode=TwoWay}" Height="30" />
Another option is to declare your custom dependency property with BindsTwoWayByDefault flag, like this:
public static readonly DependencyProperty FileNameProperty =
DependencyProperty.Register("FileName",
typeof(string),
typeof(FileSelectorTextBox),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
Also when you change your custom dependency property from inside your control use SetCurrentValue method instead of directly assigning the value using property setter. Because if you assign it directly you will break the binding.
So, instead of:
FileName = dlg.FileName;
Do like this:
SetCurrentValue(FileNameProperty, dlg.FileName);
Change as following:
<TextBox Name="txtFileName"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Grid.Column="0"
Text="{Binding FileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
I have an issue whereby I am not receiving updates through my bindings.
I have a label which is bound to the ExtentWidth of the TextBox property via the DataContext.
My binding initially works and displays the value of 0 in the label however it does not update after this.
ExtentWidth is a read only property, I'm not sure if this affects the binding in any way but I have a label the binds to the text when it is set so I know it can receive updates. (button updates text and label is updated)
below is some code to demonstrate my issue.
Xaml
<Window x:Class="TestHarnesses.Views.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel>
<ContentPresenter x:Name="ContentPresenter" Content="{Binding}"></ContentPresenter>
<Label x:Name="lblExtentWidth"
Content="{Binding ExtentWidth, Mode=OneWay}"/>
<Label x:Name="lblText"
Content="{Binding Text, Mode=OneWay}"/>
<Button Content="Different Jibber Jabber" Click="ButtonBase_OnClick"/>
</StackPanel>
</Grid>
</Window>
Code behind
using System.Windows;
using System.Windows.Controls;
namespace TestHarnesses.Views
{
///
<summary>
/// Interaction logic for Window1.xaml
///</summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TextBox tb = new TextBox(){Text = "Jibber Jabber"};
this.TestTextBox = tb;
}
public TextBox TestTextBox
{
get { return (TextBox)GetValue(TestTextBoxProperty); }
set { SetValue(TestTextBoxProperty, value); }
}
// Using a DependencyProperty as the backing store for TestTextBox. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestTextBoxProperty =
DependencyProperty.Register("TestTextBox", typeof(TextBox), typeof(Window1), new PropertyMetadata(OnTestTextBoxProperty));
private static void OnTestTextBoxProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Window1) d).DataContext = (TextBox) e.NewValue;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
TestTextBox.Text = "Different Jibber Jabber";
}
}
}
Your View is not being notified about changes to the ExtentWidth property because ExtentWidth is not a DependencyProperty, nor does the TextBox class implement INotifyPropertyChanged. Also, there does not appear to be a corresponding Changed event associated with this property.
If you want to update your view automatically with the most recent ExtentWidth then you'll need to listen to a different property/event (perhaps the SizeChanged event?) which gets updated simultaneously.
I have one UserControl LetterInRowUserControl with LongListSelector and ItemTemplate in xaml part
<UserControl>
...
<UserControl.Resources>
<DataTemplate x:Key="itemTemplate">
<local:LetterControl x:Name="letterControl" VerticalAlignment="Top" Width="50" Letter="{Binding}"/>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<phone:LongListSelector x:Name="lls" LayoutMode="List" ItemTemplate="{StaticResource itemTemplate}" />
</Grid>
</UserControl>
cs part:
public partial class LetterInRowUserControl : UserControl
{
public LetterInRowUserControl()
{
InitializeComponent();
LetterControl.TextChanged += (o, ev) =>
{
var conv = new StringToListConverter();
ContainsLetters = (string)conv.ConvertBack(lls.ItemsSource, typeof(string), null, CultureInfo.CurrentCulture);
};
}
public static readonly DependencyProperty ContainsLettersProperty =
DependencyProperty.Register("ContainsLetters", typeof (string), typeof (LetterInRowUserControl), new PropertyMetadata(default(string),
(o, args) =>
{
var control = (LetterInRowUserControl) o;
var conv = new StringToListConverter();
control.lls.ItemsSource = (ObservableCollection<string>)conv.Convert(args.NewValue, typeof(string), null, CultureInfo.CurrentCulture);
}));
public string ContainsLetters
{
get
{
return (string) GetValue(ContainsLettersProperty);
}
set
{
SetValue(ContainsLettersProperty, value);
}
}
}
then I have second Usercontrol - LetterControl which is inside itemstemplate of LetterInRowUserControl
xaml part:
<UserControl>
...
<Grid x:Name="LayoutRoot">
<Grid>
<Button x:Name="hidden" Height="0" Width="0"/>
<TextBox x:Name="textBox"
TextWrapping="Wrap" VerticalAlignment="Top" Height="84" Margin="-12" FontFamily="Segoe WP Semibold" FontSize="33.333" TextAlignment="Center" GotFocus="textBox_GotFocus" LostFocus="textBox_LostFocus" MaxLength="1" TextChanged="textBox_TextChanged"/>
</Grid>
</Grid>
</UserControl>
and cs part:
public partial class LetterControl : UserControl
{
public static event TextChangedEventHandler TextChanged;
public static readonly DependencyProperty LetterProperty =
DependencyProperty.Register("Letter", typeof(string), typeof(LetterControl), new PropertyMetadata(default(string),
(o, args) =>
{
var control = (LetterControl)o;
control.textBox.Text = (string)args.NewValue;
}));
public string Letter
{
get { return (string)GetValue(LetterProperty); }
set
{
SetValue(LetterProperty, value);
}
}
public LetterControl()
{
InitializeComponent();
}
private void textBox_GotFocus(object sender, RoutedEventArgs e)
{
textBox.SelectAll();
}
private void textBox_LostFocus(object sender, RoutedEventArgs e)
{
textBox.Text = textBox.Text.ToUpper();
}
private void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
SetValue(LetterProperty,textBox.Text);
if(textBox.Text.Length>=1)
hidden.Focus();
if (TextChanged != null) TextChanged.Invoke(sender, e);
}
}
Basically there are two UserControls: LetterInRowUserControl and LetterControl.
LetterInRowUserControl has LongListSelector with LetterControl inside its ItemTemplate.
The problem is that if I change Property Letter from inside LetterControl, it doesn't affect ItemSource in LongListSelector in LetterInRowUserControl. Conversely (if I change ItemSource directly) it works without problems.
/intended to one Windows Phone 8 App
... something like listbox with textboxes of one letter - problem is that if I edit a letter ItemSource of listbox doesn't change.
EDIT: After hard trying I have succeeded. And my code is working. Main problem was that I was using ObservableCollection<string> and not ObservableCollection<CustomObjectWithINotifyPropChangedImplementation>. So the value wasn't automatically changed (ObservableCollection did not fulfill its function).
I've looked through multiple solutions on here and the internet on how to solve this but I can't seem to get it. I'm just trying to make a simple login for an application I'm developing. Eventually I'll have it connected to an SQL database on a server but forget that right now. Here's the code:
<Controls:MetroWindow x:Class="ScotiaPlayTrade.Wpf.Application.LoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ff="clr-namespace:ScotiaPlayTrade.Wpf.Application"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:System="clr-namespace:System;assembly=mscorlib" Title="Authentication"
Height="400" Width="600" WindowStartupLocation="CenterScreen" TitleForeground="#999988"
ResizeMode="NoResize" WindowStyle="None" WindowState="Normal" ShowMaxRestoreButton="False">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--Username Layout-->
<TextBlock
Margin="10,10,10,10"
Grid.Column="0"
Grid.Row="0">
User Name
</TextBlock>
<TextBox
Margin="10,10,10,10"
x:Name="userName"
Grid.Column="5"
Grid.Row="0"
Text="{x:Static System:Environment.UserName}"
IsReadOnly="True">
</TextBox>
<!--Password Layout-->
<TextBlock
Margin="10,10,10,10"
Grid.Column="0"
Grid.Row="1">
Password
</TextBlock>
<PasswordBox
Margin="10,10,10,10"
Width="200"
x:Name="PasswordBox"
ff:PasswordBoxAssistant.BindPassword="true" x:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
Grid.Row="1">
</PasswordBox >
<!--Buttons Layout-->
<StackPanel Margin="3,3,3,3" Orientation="Horizontal" Grid.Column="1" Grid.Row="2">
<Button
Margin="10,10,10,10"
Click="Login_Click"
IsDefault="True">
Login
</Button>
<Button
Margin="10,10,10,10"
Click="Exit_Click"
IsCancel="True">
Exit
</Button>
<Button
Margin="10,10,10,10"
Click="AddNewUser_Click"
IsCancel="True">
New User
</Button>
</StackPanel>
</Grid>
That's my LoginWindow.xaml and here's my cs code for the window.
public partial class LoginWindow
{
public LoginWindow()
{
InitializeComponent();
}
//Clicking Login Button
private void Login_Click(object sender, RoutedEventArgs e)
{
//Check that password field is not null
if (PasswordBox != null)
{
//Check that username matches password, if it matches then open main window
//Launch the main window after authentication is complete
MainWindow myMainWindow = new MainWindow();
myMainWindow.Show();
//Close the login screen
Close();
}
else
{
MessageBox.Show("Password field cannot be empty!");
}
}
//Clicking Exit Button
private void Exit_Click(object sender, RoutedEventArgs e)
{
Close();
}
//Clicking Exit Button
private void AddNewUser_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Not yet implemented");
}
}
I've tried everything with passwordbox it keeps giving me an error that WPF doesn't support it, things like PasswordBoxAssistant, and Helper. Would appreciate any help. Here are the error messages:
Error 1 PasswordBoxAssistant is not supported in a Windows Presentation Foundation (WPF) project.
Error 5 The property 'PasswordBoxAssistant.BindPassword' does not exist in XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml'. Line 50 Position 13.
Ok so now I made a class called PasswordValidation and added the PasswordBoxAssistant code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace ScotiaPlayTrade.Wpf.Application
{
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPassword =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
"BindPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPassword =
DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
if (d == null || !GetBindPassword(d))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPassword, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPassword);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPassword);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPassword, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPassword);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPassword, value);
}
}
}
The following are the errors I get:
Error 1 The name "PasswordBoxAssistant" does not exist in the namespace "clr-namespace:ScotiaPlayTrade.Wpf.Application". 50 13
Error 2 PasswordBoxAssistant is not supported in a Windows Presentation Foundation (WPF) project. 50 58
Error 3 The attachable property 'BindPassword' was not found in type 'PasswordBoxAssistant'. 50 13
I've got a textbox where I have this:
<KeyBinding Command="{Binding MyCommand}" Key="Tab"/>
Problem is it swallows the Tab and doesn't tab to the next control.
How can I trap the Tab for the textbox and still preserve tabbing to the next control in the tab order?
Edit: I'm also using MVVM and MyCommand is in the ViewModel code, so that's where I need to re-throw the Tab.
It's easy to achieve, just don't use KeyBinding for this. Handle your TextBox's OnKeyDown event:
<TextBox KeyDown="UIElement_OnKeyDown" ...
Then on the code-behind, execute your command whenever Tab is pressed. Unlike KeyBinding, this won't swallow the TextInput event so it should work.
private void OnKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
// Execute your command. Something similar to:
((YourDataContextType)DataContext).MyCommand.Execute(parameter:null);
break;
}
}
I cannot find a way to set focus to a control given your question as a purely XAML solution.
I choose to create an attacted property and then through binding set the focus to next control from the Command associated with your KeyBinding in the ViewModel.
Here is the View:
<Window x:Class="WarpTab.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:WarpTab.Commands"
xmlns:Views="clr-namespace:WarpTab.Views"
xmlns:local="clr-namespace:WarpTab.ViewModels"
Title="Main Window" Height="400" Width="800">
<Window.Resources>
<c:CommandReference x:Key="MyCommandReference" Command="{Binding MyCommand}" />
</Window.Resources>
<DockPanel>
<ScrollViewer>
<WrapPanel >
<TextBox Text="First text value" >
<TextBox.InputBindings>
<KeyBinding Command="{StaticResource MyCommandReference}" Key="Tab"/>
</TextBox.InputBindings>
</TextBox>
<TextBox Text="Next text value" local:FocusExtension.IsFocused="{Binding FocusControl}" />
<Button Content="My Button" />
</WrapPanel>
</ScrollViewer>
</DockPanel>
</Window>
Here is the ViewModel:
using System.Windows.Input;
using WarpTab.Commands;
namespace WarpTab.ViewModels
{
public class MainViewModel : ViewModelBase
{
public ICommand MyCommand { get; set; }
public MainViewModel()
{
MyCommand = new DelegateCommand<object>(OnMyCommand, CanMyCommand);
}
private void OnMyCommand(object obj)
{
FocusControl = true;
// process command here
// reset to allow tab to continue to work
FocusControl = false;
return;
}
private bool CanMyCommand(object obj)
{
return true;
}
private bool _focusControl = false;
public bool FocusControl
{
get
{
return _focusControl;
}
set
{
_focusControl = value;
OnPropertyChanged("FocusControl");
}
}
}
}
Here is the code to define the attached property that I found in the following answer.
using System.Windows;
namespace WarpTab.ViewModels
{
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));
private static void OnIsFocusedPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus(); // Don't care about false values.
}
}
}
}
Why don't you just use this code in your command handler?
private void MyCommandHandler(){
// Do command's work here
TraversalRequest request = new TraversalRequest(FocusNavigationDirection.Next);
request.Wrapped = true;
control.MoveFocus(request);
}
That's basically what 'Tab' does, so if you do the same, you're good to go. (Of course reverse the direction if you have a command with Shift-Tab.
I actually wrapped this into an extension method like so...
public static class NavigationHelpers{
public static void MoveFocus(this FrameworkElement control, FocusNavigationDirection direction = FocusNavigationDirection.Next, bool wrap = true) {
TraversalRequest request = new TraversalRequest(direction);
request.Wrapped = wrap;
control.MoveFocus(request);
}
}
...meaning the prior code becomes even simpler, like this...
private void MyCommandHandler(){
// Do command's work here
Control.MoveFocus();
}
...and if you don't know what the currently focused control is, you can just do this...
(Keyboard.FocusedElement as FrameworkElement).MoveFocus();
Hope this helps! If so, much appreciated if you vote me up or mark it as accepted!
Had the same problem, came across this thread and took me a while to find the best answer. Reference: Use EventTrigger on a specific key
Define this class:
using System; using System.Windows.Input; using System.Windows.Interactivity;
public class KeyDownEventTrigger : EventTrigger
{
public KeyDownEventTrigger() : base("KeyDown")
{
}
protected override void OnEvent(EventArgs eventArgs)
{
var e = eventArgs as KeyEventArgs;
if (e != null && e.Key == Key.Tab)
{
this.InvokeActions(eventArgs);
}
}
}
The xaml for your text box:
<TextBox x:Name="txtZip"
Text="{Binding Zip, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding ZipLookup.GetAddressByZipKeyCommand}" CommandParameter="{Binding ElementName=txtZip, Path=Text}" />
</TextBox.InputBindings>
<i:Interaction.Triggers>
<iCustom:KeyDownEventTrigger EventName="KeyDown">
<i:InvokeCommandAction Command="{Binding ZipLookup.GetAddressByZipKeyCommand}" CommandParameter="{Binding ElementName=txtZip, Path=Text}" />
</iCustom:KeyDownEventTrigger>
</i:Interaction.Triggers>
</TextBox>
In your window or user control root tag include these attributes:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:iCustom="clr-namespace:[NAMESPACE FOR CUSTOM KEY DOWN CLASS]"