wpf: changing the value of a Dependency Property from viewmodel - wpf

I have a custom usercontrol with a custom dependency property of type string. It should be bound twoway to a textbox, which is in a surrounding app, like this:
<Textbox Name="TextBoxInHostApp"/>
<ctrl:MyControl
....
MyString = "{Binding ElementName=TextBoxInHostApp, Path=Text, Mode=TwoWay}"
</ctrl:MyControl>
Problem:
The Control should have the functionality to reveal the value of the dependency property also!
In MyControl, I have a simple string property "Name", which should update the MyString dependeny property value.
So normally, if MyString was not bound to the outside world, you would code in the xaml where the control is used:
<Textbox Name="TextBoxInHostApp"/>
<ctrl:MyControl
....
MyString = "{Binding Name}"
</ctrl:MyControl>
But that's not available in my case.
Attempt 1:
I used a trigger inside the usercontrol xaml:
<UserControl.Style>
<Style>
<Style.Setters>
<Setter Property="local:MyControl.MyString" Value="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</Style.Setters>
</Style>
Now this trigger doesn't work, because the "local value" of the dp is already set here:
MyString = "{Binding ...
if i comment out:
<!--<MyString = "{Binding ...
...the trigger works fine, because the "local value" isn't set.
next attempt:
I try the hard way and code in MyControls viewmodel:
public string Name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChanged("Name");
MyOwningUICtrl.SetValue(MyControl.MyStringProperty, value); // ??
}
}
I have to pass a DependencyObject to call a SetValue, which is unpleasant for MVVM. There must be better solutions for this scenario ?

This is by design
Per WPF's Dependency Property Precedence List, values specified locally within a <Tag> will always take precedence over values specified in a Style or a Trigger
A common problem is setting a value in a <Tag>, then trying to change it in a Style.Trigger. The solution is to set the default value as a <Setter> in the <Style> instead of in the <Tag>, which will then allow the triggered value to take precedence over the styled value
<Style>
<!-- Default Value
<Setter Property="local:MyControl.MyString"
Value="{Binding ElementName=TextBoxInHostApp, Path=Text, Mode=TwoWay}"/>
<Style.Triggers>
<Trigger ...>
<!-- Triggered Value -->
<Setter Property="local:MyControl.MyString"
Value="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
</Style.Setters>
</Style>
But I don't actually see a Trigger defined in your post anywhere, so I'm not really sure if this is your problem or not.
I'm a bit unclear about the exact structure of your code, but keep in mind that a Dependency Property is kind of like a pointer to another value. You can set it to point to TextBox.Text, or to point to DataContext.Name, but not both.
If you want to set both TextBox.Text and MyUserControl.MyString to point to DataContext.Name, then you should bind both properties to "{Binding Name}" like Natxo suggests.
Or if you are trying to bind MyString to UserControl.Name if MyString is not specified in the <ctrl:MyControl ...> tag, then you can try using a PriorityBinding (example here), or just expose the Name property to the outside world to let outside elements use the control like <ctrl:MyControl Name="{Binding ...}" />

If you display in both the Textbox and Mycontrol the same value... why dont you bind them to that property? :
<Textbox Name="TextBoxInHostApp" Text="{Binding Name}"/>
<ctrl:MyControl
....
MyString="{Binding Name}"
</ctrl:MyControl>

Related

Why do I need to specify ElementName and DataContext in a binding?

To familiarize myself with WPF and MVVM concepts I built a visual representation of a Sudoku board.
My (simplified) setup looks like this (no custom code-behind in views anywhere):
I have a MainWindow.xaml:
<Window x:Class="Sudoku.WPF.MainWindow">
<Window.DataContext>
<models:MainWindowViewModel/>
</Window.DataContext>
<ctrl:SudokuBoard DataContext="{Binding Path=GameViewModel}"/>
</Window>
My MainWindowViewModel:
class MainWindowViewModel
{
public MainWindowViewModel()
{
IGame g = new Game(4);
this.GameViewModel = new GameViewModel(g);
}
public IGameViewModel GameViewModel { get; private set; }
}
SudokuBoard is a UserControl. Its DataContext is set to GameViewModel as per above.
Relevant parts of GameViewModel, Elements is populated in the ctor, Possibilities is set via a command:
public IList<CellViewModel> Elements { get; private set; }
private bool _showPossibilities;
public bool ShowPossibilities
{
get { return _showPossibilities; }
set
{
_showPossibilities = value;
OnPropertyChanged();
}
}
In SudokuBoard.xaml I have:
<ItemsControl x:Name="SudokuGrid" ItemsSource="{Binding Path=Elements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource ToggleContentStyle}"
Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Elements is a collection of CellViewModels generated in the constructor of GameViewModel.
Now to the question: my ToggleContentStyle as defined in <UserControl.Resources>:
<Style x:Key="ToggleContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource valueTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=DataContext.ShowPossibilities, ElementName=SudokuGrid}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource possibilityTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
(both ContentTemplates just show other properties of a single CellViewModel in different representations)
Question 1: I have to explicitly reference DataContext in order to get to the ShowPossibilities property. If I leave it out, so that Path=ShowPossibilities, I get a UniformGrid with the ToString() representation of CellViewModel. My assumption is that that is because the style is referenced from the ItemTemplate, with it's binding set to a single CellViewModel. Is that assumption valid?
Question 2: When I omit the ElementName part, I also get the ToString() representation of CellViewModel. Now I'm really confused. Why is it needed?
Datacontext is a dependency property which is marked as inherits. That means its inherited down the visual tree.
When you bind the default place it's going to look for a source is in the datacontext.
This is the simple situation.
Say you have a window and that has datacontext set to WindowViewmodel and stick a textbox in that Window. You bind it's Text to FooText. This means the textbox goes and looks for a FooText property in that instance of WindowViewmodel.
All pretty simple so far.
Next...
You use elementname.
What that does is says go and take a look at this element. Look for a property on that. If you did that with our textbox above then it would expect a dependency property FooText on whatever you point it to.
Datacontext is a dependency property.
And when you do:
"{Binding FooProperty
This is shorthand for:
"{Binding Path=FooProperty
Where FooProperty is a property path, not =just the name of a property.
Which is maybe worth googling but means you can use "dot notation" to walk down the object graph and grab a property on an object ( on an object.... ).
Hence DataContext.Foo or Tag.Whatever ( since tag is another dependency property a control will have ).
Let's move on to some other complications.
The datcontext is inherited down the visual tree but there's a few of gotchas here. Since
some things look like they're controls but are not ( like datagridtextcolumn ). Templated things can be tricky. Itemscontrols are a kind of obvious and relevent special case.
For an itemscontrol, the datacontext of anything in each row is whichever item it's presented to from the itemssource. Usually you're binding an observablecollection of rowviewmodel to that itemssource. Hence ( kind of obviously ) a listbox or datagrid shows you the data from each rowviewmodel you gave it in each row.
If you then want to go get a property is not in that rowviewmodel you need to tell it to look somewhere else.
When you specify an element in Binding (eg ElementName=SudokuGrid), the Path has to refer to any property of that element. Because this element is a wpf control, DataContext is one of it's properties but ShowPossibilities isn't. So if you do just Path=ShowPossibilities it will not be able to find that path at all.
If you don't specify element in Binding at all then it defaults to the DataContext associated with the control. If the associated DataContext doesn't have the property ShowPossibilities it will not be able to find it.
PS: If you want to debug wpf UI to see what the DataContext is at run-time you could use utility like Snoop.

Change Default Binding/Textbox StringFormat in xaml

I am creating a form with a lot of textboxes that have text attributes as declared below:
Text="{Binding Path=SomeField, StringFormat='\{0:#,##0.##\}', Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, Source={StaticResource statementsMainsViewSource}}"
In order to save bulk space & keystrokes, I want to know if there is a way to setup default textboxes in each window using styles (eg somewhat similar to the incorrect code below):
<Style TargetType="{x:Type TextBox}">
<Setter Property="StringFormat" Value="\{0:#,##0.##\}" />
</Style>
Unfortunately, it's the binding stuff that has the property, not the text box, and i don't know how to set a binding style.
Can anyone point me to the correct syntax for changing default binding StringFormats - or can anyone suggest how I can do what I'm trying?
cheers and tia
mcalex
Try this
public class TextBinding:Binding
{
public TextBinding()
{
Mode = BindingMode.TwoWay;
StringFormat = #"\{0:#,##0.##\}";
ValidatesOnExceptions = true;
NotifyOnValidationError = true;
}
}
xmlns:myBinding="clr-namespace:WpfApplication2">
<Grid>
<TextBox Text="{myBinding:TextBinding Path=SomeProperty}"/>
</Grid>
Now you can Bind this way to all Textboxes without setting the above 4 properties of binding they will be automatically applied. I hope this will help you to get an idea.

How to trigger cellTemplateSelector when Items changed

I have 2 templates for DataGrid's CellTemplate. When I change the items, it won't help me select the template for me, my DisplayModeTemplateSelector won't even be called!
What I'm wondering is if there is a way to trigger this CellTemplateSelector again when items changed? How to refresh CellTemplate in DataGrid or ListView When Content Changes
<DataGridTemplateColumn x:Name="colorRange"
Width="*"
Header="Color Range">
<DataGridTemplateColumn.CellTemplateSelector>
<local:DisplayModeTemplateSelector HeatMapTemplate="{StaticResource heatMapTemplate}" ThreshHoldTemplate="{StaticResource threshHoldTemplate}" />
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
I found this blog
http://dotdotnet.blogspot.com/2008/11/refresh-celltemplate-in-listview-when.html
I think this is similar with my problem, but I really can't understand him! Can anyone explain it?
The solution in the blog post will not work with the DataGrid control because the DataGridTemplateColumn class doesn't belong to the Visual Tree, and even when I tried to bind it to a static class, I didn't suceed because of strange exceptions after property changes.
Anyway there is two possible ways to solve this problem.
1) The easier way.
Using the ObservableCollection class.
var itemIndex = 0;
var currentItem = vm.Items[itemIndex];
//Change necessary properties
//..
vm.Items.Remove(currentItem);
vm.Items.Insert(itemIndex, currentItem);
2) The more complex way.
You can add to your item class the property which returns the object itself.
public ItemViewModel(/*...*/)
{
this.SelfProperty = this;
//...
}
public ItemViewModel SelfProperty { get; private set; }
public void Update()
{
this.SelfProperty = null;
this.OnPropertyChanged("SelfProperty");
this.SelfProperty = this;
this.OnPropertyChanged("SelfProperty");
}
After that you can use the ContentControl.ContentTemplateSelector instead of the CellTemplateSelector like this:
<DataGridTemplateColumn Header="Color Range">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding SelfProperty}" ContentTemplateSelector="{StaticResource mySelector}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And when you change the property, call the Update method somehow:
currentItem.SomeDataProperty = "some new value";
//Or you can add this method call to the OnPropertyChanged
//so that it calls authomatically
currentItem.Update();
The reason why I've set a null value to the SelfProperty in the Update method first, is that the Selector will not update a template until the Content property is completely changed. If I set the same object once again - nothing will happen, but if I set a null value to it first - changes will be handled.
The easy way is to hook the Combo Box's Selection Changed event, and reassign the template selector. This forces a refresh.
In XAML (assume the rest of the DataGrid/ComboBoxColumn:
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.Gates, UpdateSourceTrigger=PropertyChanged}"/>
<EventSetter Event="SelectionChanged" Handler="GateIDChanged" />
</Style>
That refers to this DataGridTemplateColumn:
<DataGridTemplateColumn x:Name="GateParamsColumn" Header="Gate Parameters" CellTemplateSelector="{StaticResource GateParamsTemplateSelector}"></DataGridTemplateColumn>
And in the code behind:
private void GateIDChanged(object sender, SelectionChangedEventArgs eventArgs)
{
var selector = GateParamsColumn.CellTemplateSelector;
GateParamsColumn.CellTemplateSelector = null;
GateParamsColumn.CellTemplateSelector = selector;
}

WPF Datagrid selecteditem = null in MVVM

I'm trying to work with a datagrid using the MVVM pattern. The problem is that whenever I change the VM property which is binded to SelectedItem to null, the View doesn't "deselect" the currently selected item. This is my binding in xaml:
<DataGrid Grid.Column="0" Grid.Row="0"
ItemsSource="{Binding Path=Users}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
SelectedItem="{Binding Path=SelectedUser, Mode=TwoWay}">
The SelectedItem binding works from the view to the VM thus in the SelectedUser property I always have the selected object. The problem is that in the VM I'm doing some stuff which sometimes changes the SelectedUser property to null so I would expect the datagrid to deselect the row as well. Instead, it remains selected and if I try to click on the same row, the property doesn't update. If I click on any other row, the property changes as expected.
Is there a way to make the datagrid deselect if it's binded property is set to null? Also I'm looking for a MVVM solution as I don't want to write code behind. I can solve this by writing code behind so don't waste time offering such solutions :)
l.e.: this is my property in the VM:
public RPLUser SelectedUser
{
get
{
return selectedUser;
}
set
{
selectedUser = value;
OnPropertyChanged("SelectedUser");
}
}
Thanks in advance!
I recommend to check the Output Window in visual studio and see if any Binding is failing.
Are you sure when you select something, the selection updates into the SelectedUser property?
Did u put a breakpoint in setter of SelectedUser and see that it is hitting when you select something on the datagrid?
The reasons for this Binding to break could be many ...
SelectedUser is of different type than individual Users.
SelectedUser does not match by reference with any items in Users.
How and where are you setting null?
The following code in my case works perfectly fine...
<tk:DataGrid MaxHeight="200" AutoGenerateColumns="False"
ItemsSource="{Binding}"
SelectedItem="{Binding MySelItem,
ElementName=MyDGSampleWindow,
Mode=TwoWay}"
IsReadOnly="True">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Key"
Binding="{Binding Key,
Mode=OneWay}"/>
<tk:DataGridTextColumn Header="Value"
Binding="{Binding Value,
Mode=OneWay}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
When I set MyDGSampleWindow.MySelItem as null, the datagrid propertly deselects. Perhaps you might need to give us more input on how are you actually setting the value as null.
Did you try setting IsSynchronizedWithCurrentItem="True" in the xaml properties for the DataGrid? AFAIK, this will allow you to unselect it by setting the SelectedUser to null.
I cannot test it at the moment, but you could also try to add this in the setter of your property:
set
{
selectedUser = value;
OnPropertyChanged("SelectedUser");
ICollectionView collectionView = CollectionViewSource.GetDefaultView(Users);
collectionView.MoveCurrentTo(selectedUser);
}
(For ICollectionView to do anything, you will need to have IsSynchronizedWithCurrentItem set)
Like I said, I cannot test this right now. Also, the setter of the property is probably not the best place to put it. Maybe create an event handler for the PropertyChangedevent locally and put that logic there.
Let me know if it helps, else I'll see if I can run a short test...
Yeah may need to add the XAML UpdateSourceTrigger to update the UI.
SelectedItem="{Binding SomeProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
The DataGrid will not Deselect it automatically as DataGridRow's IsSelected property should be set to False.
You can do that by Setting a style on DataGrid.. like
<Style x:Key="dataGridRowStyle"
BasedOn="{StaticResource {x:Type WPFToolkit:DataGridRow}}"
TargetType="{x:Type WPFToolkit:DataGridRow}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
</Style>
The IsSelected property should be the of the object i.e in your case RPLUser should have a property Isselected
Then before you set the SelectedUser to null... just do SelectedUser.IsSelected=False
And dont forget to attach this style to the DataGridRowStyle in Datagrid
I am using WPFToolkit you can modify the style if you are targetting .NET 4.0

Trigger when class field changed

I have created a DataGridCellTemplate where I have an Image control. By default it's Source property is X. I fill DataGrid with objects of my own class (have also implemented INotifyPropertyChanged interface).
I would like to change Source property of Image control when some boolean variable changes from False to True.
Should I use a trigger? If yes, how? Or maybe it should be done in c# code?
I could make 2 image controls, bind and control their Visible property, but it's lame solution I think.
Would appreciate any help.
In your DataTemplate try the following:
<DataTemplate>
<Image Name="Image" Source="X"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding BooleanProperty}" Value="True">
<Setter Property="Source" TargetName="Image" Value="Y" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Where BooleanProperty is the property that triggers the source-shift. Note that the Image must have a name - and that should be used in the Setter-tag. In the example - I change the source from 'X' to 'Y'
Hope this helps!
You should see if a Converter will do what you're wanting. You write one in code by creating a class which implements the IValueConverter interface (MSDN has an example on their site).
You would then declare the ValueConverter as a StaticResource like the following (you'll have to declare the local namespace if you don't already have it):
<local:BoolToImageConverter x:Key="imageConverter" />
To use it, you then bind the ImageControl's Source property to the Boolean property and specify the converter in the binding. An example follows:
<Image Source={Binding Path=IsImageShown, Converter={StaticResource imageConverter}} />
One more thing to be aware of is that the converter cannot just return a string containing a URI to an image location. It should return an ImageSource such as a BitmapImage.

Resources