Properly bind DependencyProperty of UserControl in MVVM - c#

I wanted to create a textbox that can search for files and also keeps track of previously used files. So I made a user control with a DependecyProperty that should give me the current text of the textbox and a button. But everytime I try to bind to the DependencyProperty, the property that binds to it remains empty. In short, the control looks like this:
<UserControl
<!-- ... -->
x:Name="PTB">
<AutoSuggestBox x:Name="SearchBox"
Text="{Binding ElementName=PTB, Path=FilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding PickFileCommand}" />
</UserControl
I have this simple ViewModel for the user control
public string FilePath
{
get => _filePath;
set => SetProperty(ref _filePath, value);
}
public async Task PickFile()
{
// ...
}
and this code-behind for the user control
public readonly static DependencyProperty FilePathProperty =
DependencyProperty.Register("FilePath", typeof(string), typeof(PathTextBox), new PropertyMetadata("", new PropertyChangedCallback(OnTextChanged)));
public string FilePath
{
get => (string)GetValue(FilePathProperty);
set => SetValue(FilePathProperty, value);
}
private static void OnTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
if (dependencyObject is PathTextBox ptb && e.NewValue is string s)
{
ptb.SearchBox.Text = s;
ptb.FilePath = s;
}
}
And when I try to use it like this in my MainPage.xaml:
<customcontrols:PathTextBox x:Name="SearchBox"
KeyUp="SearchBox_KeyUp"
FilePath="{Binding ScriptFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
and MainPage.xaml.cs
private async void SearchBox_KeyUp(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Enter)
{
await ViewModel.OpenSqlFile(ViewModel.ScriptFilePath);
}
}
then ViewModel.ScriptFilePath remains empty, even though I did bind to it. I tried a couple of different things with x:Bind etc., but I couldn't find a way to cleanly implement it in MVVM. I'm using the CommunityToolkit.Mvvm library, if that helps. Any ideas?

From your code, I assume that you have the ViewModel in MainPage.xaml.cs. Then you need to add ViewModel to you binding code.
<customcontrols:PathTextBox
x:Name="SearchBox"
KeyUp="SearchBox_KeyUp"
FilePath="{Binding ViewModel.ScriptFilePath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
or even better, use x:Bind ViewModel.ScriptFilePath.

Related

WPF DependencyProperty OnPropertyChanged returns default value as NewValue if using DataTemplate on UserControl

I have an application in which I set the content of a contentpresenter, dependent on the datatype by a datatemplate (see MainWindow). The Datatemplate is a usercontrol, which is actually datatype specific. (The small example below is only for demonstration, but in my "real" application the user shall be able to switch between different data.)
The usercontrol (UserControl1) has a DependencyProperty which I assign a value (in my application this is actually a binding to a VM, just set it to a string in example for simplicity).
Setting the value is still working fine. However In my UserControl I need to react to changes of the DependencyProperty to change the view of my UserControl (or later on CustomControl). So I implemented a OnPropertyChangend method.
When application starts OnPropertyChanged works as I expect it and I get the "correct" newvalue of my DependencyProperty. However, if I change my VM (i.e. my datatemplate changes) during runtime by clicking on a button, OnPropertyChanged returns the DependencyProperty's defaultvalue.
In my small example application, I can see that the value is set correctly, as the Textblock content changes to the correct value.
It only seems that OnPropertyChanged gets fired before my DependencyProperty's value gets the new value. So, it's not possible for me to react on the new value.
It is not really clear why this happens. Seems to have something to do with the order in which WPF resolves internal stuff?
Does anyone have a clue, how I can fix this behavior and get access to the current/last value when changing my VM and don't miss an update? As stated out before, I need to react on that value.
Maybe I am doing something totally stupid here. Is the approach I decided to use here a bad one? Are DataTemplates the wrong approach to switch between two pairs? What would be a better approach then? However, I guess it won't be possible to avoid the DependencyProperty and the UserControl in my application.
MainWindow.xaml
<!--MainWindow.xaml -->
<Grid>
<StackPanel>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ContentPresenter Content="{Binding ActiveVM}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<local:UserControl1 MyProperty="Test1"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<local:UserControl1 MyProperty="Test2"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
vmParent = new VMParent();
DataContext = vmParent;
var vm1 = new VM1();
var vm2 = new VM2();
}
VMParent vmParent;
private void Button_Click(object sender, RoutedEventArgs e)
{
vmParent.ChangeActiveVM();
}
}
UserControl1.xaml
<!--UserControl1.xaml -->
<TextBlock Text="{Binding MyProperty, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}"/>
UserControl1.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(UserControl1), new PropertyMetadata("DefaultString", OnMyPropertyChangend));
private static void OnMyPropertyChangend(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == "DefaultString")
{
;
//xxxxxx
//unexpectedly i get stuck here
//Would expect/need NewValue to be Text1/Text2 to react to it
//xxxxxx
}
}
}
VMParent
class VMParent : INotifyPropertyChanged
{
public VMParent()
{
vm1 = new VM1();
vm2 = new VM2();
ActiveVM = vm1;
}
public event PropertyChangedEventHandler PropertyChanged;
VM1 vm1;
VM2 vm2;
public object ActiveVM
{
get => m_activeVM;
set { m_activeVM = value; OnPropertyChanged("ActiveVM"); }
}
private object m_activeVM;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public void ChangeActiveVM()
{
if (ActiveVM is VM1)
ActiveVM = vm2;
else
ActiveVM = vm1;
}
}
VMs are only used to apply Datatemplate
class VM1
{
}
class VM2
{
}

Bind KeyBinding Command to WPF DatePicker (MVVM)

I've been searching high and low for a way to bind the Return key to a DatePicker control in a MVVM way, but to no avail.
My current XAML Markup:
<DatePicker x:Name="DateFilter" SelectedDate="{Binding SearchDate, UpdateSourceTrigger=PropertyChanged}">
<DatePicker.InputBindings>
<KeyBinding Key="Return" Command="{Binding SearchCommand}"></KeyBinding>
</DatePicker.InputBindings>
</DatePicker>
The closest thing I found is rewriting the entire control template, but this both requires a lot of markup and screws up the original control appearance.
Right now, I've hacked a solution together by forcefully adding the DatePicker InputBindings to the underlying DatePickerTextBox from the form codebehind, but it's awful as it requires writing code behind and uses reflection:
Form CodeBehind (bound to the Loaded event of the view):
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
var fiTextBox = typeof(DatePicker)
.GetField("_textBox", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (fiTextBox?.GetValue(DateFilter) is System.Windows.Controls.Primitives.DatePickerTextBox textBox)
{
textBox.InputBindings.AddRange(DateFilter.InputBindings);
}
}
Is there a better way to do this?
(Note: I cannot bind the execution of the command to the change of the bound SearchDate property as the operation is quite expensive and I don't want it to fire every time the user picks a new date. However, I need the property to immediately refresh as the CanExecute of the command is also tied to said Date not being null.)
You could use a reusable attached behaviour:
public static class ReturnKeyBehavior
{
public static ICommand GetCommand(UIElement UIElement) =>
(ICommand)UIElement.GetValue(CommandProperty);
public static void SetCommand(UIElement UIElement, ICommand value) =>
UIElement.SetValue(CommandProperty, value);
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(ReturnKeyBehavior),
new UIPropertyMetadata(null, OnCommandChanged));
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement uie = (UIElement)d;
ICommand oldCommand = e.OldValue as ICommand;
if (oldCommand != null)
uie.RemoveHandler(UIElement.PreviewKeyDownEvent, (KeyEventHandler)OnMouseLeftButtonDown);
ICommand newCommand = e.NewValue as ICommand;
if (newCommand != null)
uie.AddHandler(UIElement.PreviewKeyDownEvent, (KeyEventHandler)OnMouseLeftButtonDown, true);
}
private static void OnMouseLeftButtonDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
{
UIElement uie = (UIElement)sender;
ICommand command = GetCommand(uie);
if (command != null)
command.Execute(null);
}
}
}
...that can be attached to any UIElement in XAML:
<DatePicker local:ReturnKeyBehavior.Command="{Binding ListViewItemMouseLeftButtonDownCommand}" />
Then you don't have to deal with any keys in the view model.
I'd probably use an interaction trigger, or whatever else your framework uses to convert events to commands, and then trap PreviewKeyDown:
<DatePicker x:Name="DateFilter" SelectedDate="{Binding SearchDate, UpdateSourceTrigger=PropertyChanged}"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<cmd:EventToCommand Command="{Binding KeyDownCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DatePicker>
And then in your view model:
private ICommand _KeyDownCommand;
public ICommand KeyDownCommand => this._KeyDownCommand ?? (this._KeyDownCommand = new RelayCommand<KeyEventArgs>(OnKeyDown));
private void OnKeyDown(KeyEventArgs args)
{
if (args.Key == Key.Return)
{
// do something
}
}

Databinding textbox read from property

I want to fill the text property from a textbox using binding. (My first try with binding).
I have this:
public string TestProperty { get; set; }
private void Window_Loaded(object sender, RoutedEventArgs e)
{
TestProperty = 'Test';
}
in xaml:
<TextBox x:Name="TextBox_Test" HorizontalAlignment="Left" Height="23" Margin="49,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="288" Text="{Binding ElementName=TextBox_Test, Path=TestProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The property is filled when the form loads. The textbox keeps empty. How can i fill the textbox?
You've got to fix a couple of things before this will work.
First, you're binding expression isn't quite right. You specified that the binding source is the TextBox by using the ElementName. That's not correct. Your source should actually be the Window since that's where your property exists. So, give your Window a name, and change ElementName to that Windowname. For example ..
<TextBox x:Name="TextBox_Test" HorizontalAlignment="Left" Height="23" Margin="49,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="288"
Text="{Binding ElementName=Window_Test, Path=TestProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Second, your Windowneeds to implement INotifyPropertyChangedfor changes on the source to be reflected on the target.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _testProperty;
public string TestProperty
{
get { return _testProperty; }
set
{
_testProperty = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TestProperty"));
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
TestProperty = "Test";
}
public MainWindow()
{
InitializeComponent();
}
}
Note that I modified the class to implement the interface, and I raise the event in the property setter.
With these changes, your binding will work. I should note that this type of binding is a bit unusual. In cases like this, it's more common for the Window to use a DependencyProperty, or for you to bind to a non-UI class (A view model, for example). You may want to look into both as you learn about binding.
Dependency Properties
MVVM Pattern

Focus on a textbox in autocompletebox?

this is my xaml:
<toolkit:AutoCompleteBox Name="signalNameEditor"
ItemsSource="{Binding MySource}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsTextCompletionEnabled="True"
FilterMode="StartsWith"
ValueMemberPath="Label"
MinimumPrefixLength="3"
MinimumPopulateDelay="800"
Style="{StaticResource autoCompleteBoxStyle}">
<toolkit:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="textBlock" Text="{Binding Label}"/>
</StackPanel>
</DataTemplate>
</toolkit:AutoCompleteBox.ItemTemplate>
</toolkit:AutoCompleteBox>
So, how could i get textblock element in my view? I tried this:
var textBlock = signalNameEditor.FindName("textBlock");
but it is wrong. So could you help me with this or redirect me to a proper solution. Thanks in advance.
Thanks for all aswers, that worked
var textBlock = ((StackPanel)signalNameEditor.ItemTemplate.LoadContent()).FindName("textBlock") as TextBlock;
but unfortunately I didn't get the result, that I expected. The question is how to get focus on textbox in autocompletebox, so that when focus is on autocompletebox I could write something there without double clicking.
I thought that I could do something inside my view
public void SetFocus
{
var textBlock = ((StackPanel)signalNameEditor
.ItemTemplate
.LoadContent())
.FindName("textBlock") as TextBlock;
textBlock.Focus();
}
I know that there are a lot of howto examples for setting focus like this one
autocompletebox focus in wpf
but I can't make it work for me. Is there a solution, that I could get without writing AutoCompleteFocusableBox class?
The solution was simplier. Actually i need to set focus on a textbox in a autocompletebox. For this purpose I used style defined as a regular style http://msdn.microsoft.com/ru-ru/library/dd728668(v=vs.95).aspx
After it in my view I could use the following:
public void SetFocus()
{
var textbox = this.editor.Template.FindName("Text", editor) as TextBox;
textbox.Focus();
}
You can Write extension and set custom property for textbox to make it focusable
For example you can write extension class as below
public static class FocusBehavior
{
#region Constants
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached("IsFocused", typeof (bool?),
typeof (FocusBehavior), new FrameworkPropertyMetadata(IsFocusedChanged));
#endregion
#region Public Methods
public static bool GetIsFocused(DependencyObject obj)
{
return (bool) obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
#endregion
#region Event Handlers
private static void IsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement) d;
if ((bool) e.NewValue)
uie.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() => Keyboard.Focus(uie)));
}
#endregion Event Handlers
}
Then in xaml as below:
<UserControl xmlns:behaviours="clr-namespace:Example.Views.Behaviours">
<TextBox TextWrapping="Wrap" Text="TextBox" behaviours:FocusBehavior.IsFocused={Binding IsFocused}/>
I hope that answeres your question

opening the appbar in metro style apps using binding property

My main page has the appbar and it is shared across different pages. I wrote the following code to open the appbar on the click of a gridview item.
XAML
<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen}">
Back end
private void Clock_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
App.WorldViewModel.IsAppBarOpen = true;
}
private void ThemeGridView_ItemClick(object sender, ItemClickEventArgs e)
{
App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
App.WorldViewModel.IsAppBarOpen = true;
}
WorldViewModel
private bool _IsAppBarOpen;
public bool IsAppBarOpen
{
get { return _IsAppBarOpen; }
set { base.SetProperty(ref _IsAppBarOpen, value); }
}
GridView XAML
<GridView
Grid.Row="1"
Grid.Column="1"
x:Name="ThemeGridView"
ItemsSource="{Binding Clocks}"
ItemTemplate="{StaticResource WorldClockTemplate}"
SelectionChanged="Clock_SelectionChanged"
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="ThemeGridView_ItemClick"
>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
But the appbar is not popping up when i select the gridview item. There is no binding error so its really mysterious!
There is not way to bind IsOpen property according the msdn:
Note Binding to the IsOpen property doesn't have the expected results
because the PropertyChanged notification doesn't occur when the
property is set.
<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen, **Mode=TwoWay**}">
This works for me. I use MVVM Light Toolkit.
public bool AppBarIsOpen
{
get { return this._appBarIsOpen; }
set
{
if (this._appBarIsOpen == value) { return; }
this._appBarIsOpen = value;
this.RaisePropertyChanged("AppBarIsOpen"); // without INotifyPropertyChanged it doesn't work
}
}
<AppBar
IsSticky="True"
IsOpen="{Binding Path=AppBarIsOpen, Mode=TwoWay}">
Roman Weisert's answer correctly states the likely reason for it not working, although you also must make the binding two-way as Zack Weiner suggested (I'm not sure the reason for the latter since the binding is not working in the target-to-source direction anyway). The current value of AppBar.IsOpen may not be reflected by IsAppBarOpen of your view-model. When that's the case, and you try updating the value, it's possible that no PropertyChanged event is raised since you may not actually be updating a value. Instead, you may be just setting the value from false to false or from true to true. Most SetProperty method implementations do not raise the PropertyChanged event unless there is an actual change, and I presume yours is the same.
To fix the problem, consider modifying your view-model as follows:
public bool IsAppBarOpen
{
get { return _IsAppBarOpen; } //changes initiated from UI not reflected
set //not updated from UI
{
_IsAppBarOpen = value;
base.OnPropertyChanged();
}
}
bool _IsAppBarOpen;
The notable difference from your view-model's code, is that SetProperty is not called here so PropertyChanged is raised even when the backing store equals the newly introduced value. In case your base class differs, note that mine has an OnPropertyChanged method with the signature
void OnPropertyChanged( [CallerMemberName] string propertyName = null )
that serves to raise the PropertyChanged event.
I can see from your use of the code-behind, though, that you are not really following MVVM. If MVVM is not a concern to you, then you could forgo the IsAppBarOpen property altogether and just directly set AppBar.IsOpen. As someone who religiously adheres to MVVM, however, I do not recommend that you further head in that (sinful) direction.
I had the same issue and using Caliburn Micro for WinRT and with this code worked for me:
<AppBar IsOpen="{Binding AppBarsOpen}" Name="MainAppBar" Padding="10,0,10,0" AutomationProperties.Name="Bottom App Bar">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="LeftPanel" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
<Button Name="ShowFlyout" Style="{StaticResource BookmarksAppBarButtonStyle}" />
</StackPanel>
<StackPanel x:Name="RightPanel" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right">
<Button Style="{StaticResource SaveAppBarButtonStyle}" />
</StackPanel>
</Grid>
</AppBar>
And that's your property in ViewModel:
public bool AppBarsOpen
{
get { return _appBarsOpen; }
set
{
if (value.Equals(_appBarsOpen)) return;
_appBarsOpen = value;
NotifyOfPropertyChange(() => AppBarsOpen);
}
}
Had the same issue, solved it by adding the Closed event and updating the ViewModel from the code behind. Saw no other way since TwoWay binding was not working as Roman pointed out.
XAML
<AppBar x:Name="BottomAppBar1"
AutomationProperties.Name="Bottom App Bar"
Closed="BottomAppBar1_Closed"
IsOpen="{Binding IsOpen, Mode=TwoWay}"
IsSticky="True">
C# Code behind
private void BottomAppBar1_Closed(object sender, object e)
{
MainViewModel vm = this.DataContext as MainViewModel;
vm.IsOpen = false;
}
C# MainViewModel
public const string IsOpenPropertyName = "IsOpen";
private bool isOpen = false;
/// <summary>
/// Sets and gets the IsOpen property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public bool IsOpen
{
get
{
return isOpen;
}
set
{
RaisePropertyChanging(IsOpenPropertyName);
isOpen = value;
RaisePropertyChanged(IsOpenPropertyName);
}
}
You should bind both IsOpen and IsSticky two way because otherwise you will get problems with for example having to tap two time to unselect an item (once to close the app bar and once for unselecting) and also it's the will help having your app bar behave more standarly (will prevent the app bar to pop down on tap when an item is selected).
To show the app bar you will need to do the following (the order is important):
this.IsAppBarSticky = true;
this.IsAppBarOpen = true;
and to hide it, do the following:
this.IsAppBarSticky = false;
this.IsAppBarOpen = false;
Another way to make this work without having to use a codebehind handler for app bar closed event:
public class AppBarClosedCommand
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(AppBarClosedCommand), new PropertyMetadata(null, CommandPropertyChanged));
public static void SetCommand(DependencyObject attached, ICommand value)
{
attached.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject attached)
{
return (ICommand)attached.GetValue(CommandProperty);
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Attach click handler
(d as AppBar).Closed += AppBar_onClose;
}
private static void AppBar_onClose(object sender, object e)
{
// Get GridView
var appBar = (sender as AppBar);
// Get command
ICommand command = GetCommand(appBar);
// Execute command
command.Execute(e);
}
}
then in the XAML you can use it like :
common:AppBarClosedCommand.Command="{Binding AppBarClosedCommand}"
with the command function looking like:
public void OnAppBarClosed()
{
AppBarOpen = false;
}

Categories