I am working on a project in WPF and I have a very strange case concerning my converters on a certain element.
In the following snippet:
<myCtl:Pager IsTabStop="False" Style="{StaticResource MainPager}"
DataContext="{Binding CurrentView, Converter={StaticResource SectionToPagerDriver}}"
Visibility="{Binding CurrentView, Converter={StaticResource SectionToVisibility}}"/>
The converter for 'DataContext' will fire, but the converter for 'Visibility' will not. This seems odd to me considering that they are both bound to 'CurrentView' which indeed changes. I have even tried setting the binding mode explicitly to 'TwoWay' but this does nothing to resolve the issue.
Does anyone have a clue why one binding would fire, and the other would not ?
When you set the DataContext on your Control, all other bindings will use the new object as their source.
If you check your output window, you'll see a binding error saying that there is no CurrentView property on whatever object is returned by that property.
Instead, you should just do:
<myCtl:Pager IsTabStop="False" Style="{StaticResource MainPager}"
DataContext="{Binding CurrentView, Converter={StaticResource SectionToPagerDriver}}"
Visibility="{Binding Converter={StaticResource SectionToVisibility}}"/>
Related
I need help trying to understand why this is not working. According to MSDN, TemplateBinding is what should be used when binding the property of a control in a template to a property of the control implementing the template.
Except that Template Binding is not two-way. For two-way you need to use binding and then specify the relative source as TemplatedParent.
So I have the following XAML:
template
<ItemContainerTemplate x:Key="colHeaderTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" VerticalAlignment="Center"/>
<ToggleButton Style="{StaticResource ToggleButtonStyle}" IsChecked="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay, Path=(props:VisibilityHelper.IsGroupCollapsed)}"/>
</StackPanel>
</ItemContainerTemplate>
which is used here
<dxg:GridColumn x:Name="Total" Header="Total" FieldName="field1" Width="Auto" HorizontalHeaderContentAlignment="Center" props:VisibilityHelper.IsGroupCollapsed="False" HeaderTemplate="{StaticResource colHeaderTemplate}">
<dxg:GridColumn.EditSettings>
<dx:TextEditSettings HorizontalContentAlignment="Center"/>
</dxg:GridColumn.EditSettings>
</dxg:GridColumn>
The toggle button in the template must set a dependency property on the grid column. This works fine when the template is binding to a parent ie. the controls are nested,
I just can't figure out what I am doing wrong.
MSDN ref - http://msdn.microsoft.com/en-us/library/ms742882.aspx
One of the many SO posts about this - In WPF, why doesn't TemplateBinding work where Binding does?
Thank you
Right so I have found the solution. Firstly DataTemplate does work. As #Quercus, it is all in the binding to the correct control.
In my case not the GridColumn but the GridColumnHeader. So this
IsChecked="{Binding RelativeSource={RelativeSource AncestorType=dxg:GridColumnHeader}, Path=DataContext.(props:VisibilityHelper.IsGroupCollapsed)}"
works perfectly...when bound to the correct parent.
Also as #Quercus stated, the template is actually nested and that is why this works. I used a tool called Snoop which actually shows you the visual tree of the application and then the datacontext of the selected element. Using this I solved this issue as well as 2 others I was having.
I really hope this helps someone somewhere before everyone goes to MAUI or WinUI 3.
Since the Anniversary Update (Build 14383 / 14393) you should be able to toggle the visibility of XAML elements without using a converter, like this:
<TextBlock Text="I'm not visible!" Visibility="{x:Bind IsVisibleFalse}" />
<TextBlock Text="I'm visible!" Visibility="{x:Bind IsVisibleTrue}" />
I was trying this in my project, minimum target version set to Windows 10 Anniversary Edition. Unfortunately I did not get it to work.
This code works just fine:
<StackPanel Visibility="{x:Bind ViewModel.IsUnlocked,
Converter={StaticResource BoolToVisibilityConverter}, Mode=TwoWay}">
This one doesn't (no error on compile, just does not show up when the bool value changes):
<StackPanel Visibility="{x:Bind ViewModel.IsUnlocked}>
I suspect the Mode="TwoWay" to be the issue, as you can't set it "when the binding expression ends with a cast". This code does not work as well:
<StackPanel Visibility="{x:Bind ViewModel.IsUnlocked,
Converter={StaticResource BoolToVisibilityConverter}>
So my question is: Am I misssing something or is this not working yet in a MVVM-Scenario and only with code-behind?
The default Mode is OneTime, which renders your code not working. I suggest you use OneWay, which should be usable upon casting.
Turns out x:Bind defaults to Mode=OneTime - I mistakenly thought it's Mode=OneWay.
So this does actually work:
<StackPanel Visibility="{x:Bind ViewModel.IsUnlocked, Mode=OneWay}>
Im working on a WPF project in which my view model has more than 90 properties. I have a "save" button on clicking of which I save the properties to DB. Im required to disable the Save button until at lease one of the properties has been modified. I cannot go and put OnPropertyChanged event handling for each property(simply because it is too cumbersome for 90+ properties). Is there that I can do such that even if one property is changed I should get notified so that I can enable the Save button?
<StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="{x:Static res:Resources.SaveToDB}" Command="{Binding Save}" IsEnabled="{Binding CanSaveToDB}" Margin="0,0,4,0" HorizontalAlignment="Center" Style="{StaticResource OverlayDialogButtonCentered}" FontSize="14"/>
<Button Content="{x:Static res:Resources.DiscardDBSave}" Command="{Binding DiscardSave}" Margin="0,0,4,0" HorizontalAlignment="Center" Style="{StaticResource OverlayDialogButtonCentered}" FontSize="14"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView1 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View1}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView2 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View2}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView3 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View3}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView4 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View4}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView5 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View5}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView6 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View6}"/>
</StackPanel>
<StackPanel>
<views:MySampleAppView7 DataContextChanged="MySampleApp_DataContextChanged" DataContext="{Binding MySampleApp.View7}"/>
</StackPanel>
</StackPanel>
Then each of the above View has WPF controls.I have viewmodels for each of the above View. Then there is a main view model which represents above mentioned code.
A typical solution is to implement it just once (usually on a base class) and call it from the setter on each property passing the name of the property that has changed. This is still relatively cumbersome. Another approach is to use a tool such as Fody to automatically add this logic.
If all your properties are calling a single OnPropertyChanged implementation then the logic to set the dirty flag (and so enable the Save button) can be implemented there.
As per the Documentation,
The PropertyChanged event can indicate all properties on the object
have changed by using either null or String.Empty as the property name
in the PropertyChangedEventArgs.
Two ways come to mind.
First, you don't really need to trigger a unique property change notice in each property setter because your buttons' IsEnabled properties are not gonna be binding to any one of those 90-some odd properties in your view model. What you really want I think is to have some kind of IsDirty property that gets set whenever one of the other properties change, bind that to the IsEnabled property of the buttons, and execute the OnPropertyChanged notifier when IsDirty is changed.
If you still don't want to have to do ANYTHING special in your 90 property setters, however, the other option is to handle the change event in your view controls themselves. In all TextBoxes, for example, subscribe to TextChanged. And in your event handlers, set the IsDirty property on your view model. (You can subscribe to those handlers in the controls' styles so you don't have to set the event handler 90 times.)
Barring those two ideas, I don't think there's any way to do what you want. There's no built in mechanism in C# to get notified when a class property has changed. You're gonna have to intercept the change yourself, if not in the properties' setters, then using event handlers on the controls they're bound to.
I currently have a list of objects in which my RadGridView's ItemsSource is set to. When the property "DoNotContact" of the object in the list has been set to True, I want to hide the information in the cell that contains a Phone number within my RadGridView. As you can see in my XAML, I'm setting the Visibility property within the TextBlock like so:
<telerik:GridViewDataColumn Header="Evening" DataMemberBinding="{Binding Path=EveningPhone}" Width="75" SortMemberPath="EveningPhone">
<telerik:GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Visibility="{Binding Path=DoNotContact, Converter={StaticResource BoolToVisibilityConverter}}">
<Hyperlink Click="MakeEveningCallHandler">
<TextBlock Text="{Binding Path=EveningPhone}" />
</Hyperlink>
</TextBlock>
</DataTemplate>
</telerik:GridViewColumn.CellTemplate>
</telerik:GridViewDataColumn>
When attempting to debug it, the Converter is never hit and although I can see the property "DoNotContact" has been set, the phone number still shows. The converter itself works fine as I've used it in other occasions. Again I only want to hide the information WITHIN the cell for the "Evening" Property, not the actual column itself. Any Ideas what's going wrong here? Thanks a bunch!
The code you provided works for me!
I have this datagrid that is bound to an observablecollection of items, like this:
<DataGrid ItemsSource="{Binding Path=MyItems}">
Then, one of the columns is bound to a property of MyItems through a simple converter that switches the bool to an image path.
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="DownloadedIcon" Source="{Binding Converter={StaticResource BoolToImageCheckmark}, ConverterParameter=IsDownloaded, UpdateSourceTrigger=PropertyChanged}" Width="16" Height="16" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The property itself, IsDownloaded, fully implements INotifyPropertyChanged.
This works normally, as the data displayed matches the values of the collection, and the image column properly displays the image based on the property value.
Trouble comes when the property changes. If I bind a text column directly on the property, the content will update when the property is updated. However, the image column, which passes through a converter, will not receive the notification to update.
Any ideas?
Try this:
<DataTemplate>
<Image Name="DownloadedIcon" Source="{Binding Path=IsDownloaded,Converter={StaticResource BoolToImageCheckmark}}" Width="16" Height="16" />
</DataTemplate>
Also place a breakpoint in the converter, in order to verify the binding is actually working.
Note you'll get the bound value via the Value parameter in your converter.
values passed to ConverterParameter do not react to PropertyChanged notifications. Use Path instead of ConverterParameter in your binding, then refer to the value argument in the Convert() function in your converter instead of the parameter argument.
ConverterParameter is not a dependency property and therefore you cannot bind it to a property like you tried to do it. You should bind your image source to the IsDownloaded property and convert that:
<DataTemplate>
<Image Name="DownloadedIcon" Source="{Binding Path=IsDownloaded,Converter={StaticResource BoolToImageCheckmark}}" Width="16" Height="16" />
</DataTemplate>
Problem is in your converter class.
Since your binding expression do not specify a “Path”, current DataContext use as the path and results in the DataContext object as your value in your converter class. Calculations are performing on this copy of datacontext object.
This approach will succeed at first time when binding being executed. As a result the image column properly displays the image.
Later ‘IsDownloaded’ property changes, it reflects in ObservableCollectionClass, but the image control unable to understand this change since its source property is not bound to any collection class property. Similarly as the converter class received a copy of datacontext object, property changes never reflects in converter class either.
Therefore set the image source property to collection class property 'IsDownloaded'. Any changes happens to this property will trigger the converter class with new value.
Image Name="DownloadedIcon" Source="{Binding Path=IsDownloaded,Converter={StaticResource BoolToImageCheckmark}}" Width="16" Height="16"/>
UpdateSourceTrigger not required.
in fact, you didn't bind the image to the property IsDownloaded you bind it to the whole object in the list. Path is Important.
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="DownloadedIcon" Source="{Binding Converter={StaticResource BoolToImageCheckmark}, ConverterParameter=IsDownloaded, Path=IsDownloaded, UpdateSourceTrigger=PropertyChanged}" Width="16" Height="16" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>