Cannot bind to additonal property in extended Control - wpf

I am reasonably proficient in XAML and WPF having trouble with binding to an additional control within an extended control from outside the control. Sorry, that's a real mouthful so let me explain:
I have a control that I have extended from a ComboBox and applied the template and overridden the property metadata and all that stuff and re-templated it so it looks and works as I want it to. Now, I want to add a TextBox to provide search functionality for the ComboBox which I have exposed dependency properties to determine if it is visible or not and added this to the first row of the Grid above the ItemsControl and all works fine. I have added a dependency property called IsFiltered and applied a template binding to determine if the filter is visible and from outside my control I can set this value and it all works.
However, I have added a dependency property to the extended ComboBox (MyComboBox if you like) as a string property so that I can assign a filter text property from my view model that will eventually work its way to the text box embedded within the control. The TextBox in the conrol is also bound using {TemplateBinding FilterText} dependency property, as it hooks back to my MyConboBox control and the assignment is accepted and recognised. However, while the property from my view model is set and read and interacts with the FilteText property in MyComboBox to which the TextBox inside by MyComboBox control template is also bound to, the TextBox does not trigger a change.
<TextBox
Grid.Row="0"
Margin="4"
Text="{TemplateBinding FilterText}"
BorderBrush="Red"
Visibility="{TemplateBinding IsFiltered, Converter={converters:BoolToVisibilityConverter}}"/>
Can anyone help?

Related

WPF MVVM - How to Bind Custom Control->ToggleButton.IsChecked to View->TextBox.Text

I am moving over from WinForms to WPF and trying to implement the MVVM pattern for a touchscreen application. I created several custom controls inside a WPF Control Library (dll), and I can bring these controls into the View with no issue. However, I am getting stuck on a purely academic scenario where I want a TextBox inside the View to display my custom control's ToggleButton.IsChecked property as "Checked" and "Unchecked" respectively.
To sum up, I need to know the proper way to expose properties of a control that is inside a custom user control. Then when the exposed property changes update some other control with custom data based on the property that changed.
To sum up, I need to know the proper way to expose properties of a control that is inside a custom user control. Then when the exposed property changes update some other control with custom data based on the property that changed.
You're describing dependency properties. You need to add a dependency property to your custom control, which you then bind to from inside the control, and from outside it (in your view).
The first part will depend on whether you're using a UserControl or a Control. Let's say it is a Control, then you would use a TemplatedParent binding in your ControlTemplate:
<ToggleButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsToggleChecked,Mode=TwoWay}" ... />
If on the other hand it is a UserControl, then the approach is similar, but you need to make sure the data context is right. One approach would be to use a FindAncestor binding:
<ToggleButton IsChecked="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=IsToggleChecked,Mode=TwoWay}" ... />
Now, to add the dependency property, try the Visual Studio code snippet "propdp". It should look something like this:
public bool IsToggleChecked
{
get { return (bool)GetValue(IsToggleCheckedProperty); }
set { SetValue(IsToggleCheckedProperty, value); }
}
public static readonly DependencyProperty IsToggleCheckedProperty =
DependencyProperty.Register("IsToggleChecked", typeof(bool), typeof(MyCustomControl), new PropertyMetadata(false));
And now finally you can bind your TextBox to the new dependency property:
<TextBox Text="{Binding ElementName=myCustomControl,Path=IsToggleChecked,Converter={StaticResource BoolToTextConverter}}" />
<local:MyCustomControl x:Name="myCustomControl" ... />
I assumed that you would want to make an IValueConverter "BoolToTextConverter" that converts the boolean value to the string "Checked" or "Unchecked".

Retrieving value of a label in which is binded to an element value

In my current scenario (WPF, MVVM), I have a user control which hosts a visio diagram. This user control is located on a view, next to a number of labels and a datagrid element.
The user control contains a DependencyProperty object SelectedNode which value is updated with the information received from the Visio diagram. The labels' content are binded so that they display the information contained in the SelectedNode (e.g. id, name):
<Label Grid.Row="1" Grid.Column="1" x:Name="lbNodeIdValue" HorizontalAlignment="Left"
Content="{Binding ElementName=visioControlUC, Path=SelectedNode.Id, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
Every time I change the selection in the diagram, the label's content changes as expected.
Next to this label, I would like to display a datagrid containing information based on the id displayed in the label. This is where I ran into problems, as I can't seem to be able to get the value of the Content property of the label in the viewmodel class.
I have tried using the MultiBinding property on the Content element of the label, and creating a second binding with Mode=OneWayToSource to set the value of the label's Content to a property I have defined in the viewmodel class.
What would be a proper way to retrieve this value in my viewmodel class?
Thanks,
Adrian
Ideally your Datagrid's ViewModel should get the value of the selected label from the other ViewModel. You should not rely on Views to transfer application data between ViewModels.
It sounds like the SelectedNode value originates from the UserControl, and not the ViewModel, so you'll need to bind the UserControl.SelectedNodeId to a ViewModel somewhere so the ViewModels have access to this data
<local:myUserControl x:Name="visioControlUC"
SelectedNode="{Binding SelectedNodeId}" />
If the value is needed by more than one ViewModel, I would highly recommend some kind of event system, such as MVVM Light's Messenger or Prism's EventAggregator. This would allow your ViewModels to subscribe to something like a SelectedNodeChangedEventMessage, and the ViewModel which actually contains the SelectedNodeId can broadcast that message anytime the value changes. You can find an example of both on my blog post about Communication between ViewModels.

What's the proper way to bind text to properties of a Data Grid in WPF?

I have a WPF Data Grid bound to an observable collection, which is working as intended.
What I am trying to do now is add text below it to say: "Number of selected rows: {count goes here}"
What's the proper way to do this? I could add a new property in the View Model called SelectedCount or something similar and bind to that, but it doesn't feel right. It seems redundant. Also, I could set the label text dynamically in the code behind, but I'm not sure if that's the "right" place to do this either.
Here's an example below.
EDIT:
Please pretend there's a checkbox column header whose intention is to provide check/uncheck all functionality. The state of this header checkbox should not count towards the final count.
You could use element binding to declaratively bind to the SelectedItems.Count property in XAML:
<TextBlock Text="{Binding ElementName=MyDataGrid,
Path=SelectedItems.Count, StringFormat=Number of selected rows: {0}}" />
Update
Presumably you're using MVVM, so adding a SelectedXCount property to your view model is a perfectly reasonable application of the view model. The advantage of having it in the view model is that you could unit test based on the number of selected items. E.g. if you want to check that the user can only progress (a CanNext property returns true) if the user has selected some items.
The SelectedItems property is not a DependencyProperty so can't be bound to, but there are many articles online that get around the issue when using the DataGrid in MVVM. Most of the solutions involve using a mechanism for calling a view model command on the invocation of the DataGrid's SelectionChanged event.

How to enable wpf controls through data binding on a selected item from a combo box

I am looking for a way where a control can be enable when an item from a combo box is selected. Is there a simple way through data binding when a user selects an item from a combo box that it then enables another control to be used?
If you're using MVVM, you can bind the SelectedItem of the combobox to a property in your viewmodel.
Say this is your combobox:
<ComboBox ItemsSource="{Binding widgetlist}" SelectedItem="{Binding Path=selectedwidget, Mode=TwoWay}"></ComboBox>
And this is your control:
<DockPanel IsEnabled="{Binding controlenabled}">
...
</DockPanel>
Then in selectedwidget's setter, you can change the controlenabled property to False or True. Don't forget to notify that the controlenabled property changed (or if you want, make controlenabled a DependencyProperty.)
In summary, you've got 3 properties to bind to:
widgetlist, an ObservableCollection or some other collection that is the source for your combobox
selectedwidget, an item of that collection type that changes to whatever the combobox currently has selected
controlenabled, a bool that the other controls look at to decide if they are enabled/disabled.
Like many examples in MVVM, this way may require slightly more thought and code on the outset, but will be far more maintainable and scalable later. For example, say you want some more controls to also enable/disable themselves based on the same scenario. Piece of cake: add IsEnabled="{Binding controlenabled}"> to them.
Yes. You want to bind to IsEnabled in the target control which you want to dynamically enable or disable, and use a Value Converter to convert a matching string or item from the ComboBox to a true value for being enabled.

Setting a property on the ViewModel from the View in WPF

I have a dependency property on my ViewModel which is the DataContext for my View. The ViewModel has no reference to the View. The property on the ViewModel is going to reference a control on the view, but I need to be able to set this property in XAML.
How is this possible? One thought I had was to develop a custom control which has a Property property and a Value property, so you could do something like this in the View to set the property on the ViewModel:
<PropertySetter Property="{Binding MyViewModelDependencyProperty}" Value="{Binding ElementName=aControlOnMyView" />
Before I went down this route, I wanted to check if there was any other approach I could take?
Thanks for the detailed reply Ray, but if I give you a bit more detail about the problem I'm trying to solve, you might get a better idea of why I mentioned the approach I did.
Basically, what I'm trying to do is set the focus to a textbox when the user hits a button. I've written an attached property which you can attach to the Button control, specify what the trigger event is (in this case the 'Click' event), and then what control to focus on. This works really nicely, and keeps everything in XAML.
However, I now have a use case where the focus should be set to an arbitrary text box from the click event on a button which is part of a toolbar. This toolbar is itself a user control which is sitting inside another user control, which is inside another user control! This toolbar needs to be reusable across various different forms, and each time, the control to set focus on after you click the button will be different per form.
That's why I had the idea of making the focus control (i.e. a textbox) a property on the view model itself (on my ViewModel base to be precise), and have the ViewModel base code (which the toolbar is bound to), set the focus to the control when the button is clicked (and the e.g. Add/Edit method is called on the ViewModel base).
In unit test land, the control to focus on property will be null, so it's .Focus() method just won't be called. So I can't see an issue there. My problem is then how you set the focus control property from XAML, which is why I had the PropertySetter idea.
I don't like the fact that the ViewModel has any reference to controls sitting on the view, but I can't see another way to achieve what I need. What if the logic that dictates whether to set focus to the control is quite complex? This would sit in the ViewModel surely? Therefore, is there any harm in the ViewModel having this UIElement property? It still knows nothing about the specific View it is bound to, it just knows that there is a control which it needs to set focus to when some action happens on the ViewModel.
My first reaction (and it's a strong one) is so say "Don't do that!" By giving your view model a reference to a part of your UI you are breaking the encapsulation that makes view models so powerful and useful.
For example, what if you want to unit test your view model or serialize it to disk? In each case the piece of your UI will not be present, because there will be no view at all. Your tests will miss coverage and your reconstitution will be incomplete.
If your view model actually needs references to UI objects and there is no better way to architect it, the best solution is to have the view model itself construct those controls it requires a reference to. Then your view can incorporate that control as the Content of a ContentPresenter via binding and provide a Style to configure the control, including a ControlTemplate to provide its content. Thusly:
public class MyViewModel
{
public ListBox SpecialControl { get; set; }
public MyViewModel()
{
SpecialControl = new ListBox();
}
}
and
<DataTemplate TargetType="{x:Type local:MyViewModel}">
<DataTemplate.Resources>
<Style TargetType="ListBox" ... />
</DataTemplate.Resources>
...
<ContentPresenter Content="{Binding SpecialControl}" />
</DataTemplate>
Other possibilities are:
Have the view model actually derive from the Control class, then override OnApplyTemplate() and use GetTemplateChild to find a template item whose name starts with "PART_"
Implement an attached property that takes a property name, finds that property in the DataContext, and sets it to the DependencyObject to which the property is attached.
Implement your PropertySetter idea
My option #2 would look like this:
<DataTemplate TargetType="{x:Type MyViewModel}">
...
<TextBox local:PropertyHelper.SetViewModelToThis="SpecialControl" />
...
</DataTemplate>
The code in the SetViewModelToThis PropertyChangedCallback would get the view model from the DataContext, reflect on it to find the "SpecialControl" property, then set it to the TextBox. Note that the implementation of SetViewModelToThis must take into account the possiblity that DataContext is not set right away, and that it maybe changed requiring the old setting to be removed and a new one made.
First of all, the DataContext of the control should be the ViewModel object and not a property of it. Second, when you TwoWay bind a property of ViewModel to your control, changes in the control's value will update (in your case, 'set') the value of ViewModel's property.

Resources