WPF: Master/Detail - Disable detail TextBoxes if no records? - wpf

I have an ObservableCollection of custom objects bound via a DataContext to a ListBox.
Next to the ListBox a have a group of TextBox that are bound to the current item's field. (i.e. Text={Binding Path=/SomeField})
How can I disable / grey out the record detail TextBoxes when my DataContext's ObservableCollection is empty?

You could do it with a style:
<Style TargetType="TextBox">
<Style.Triggers>
<!-- When the collection itself is null -->
<DataTrigger Binding="{Binding }" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
<!-- When the collection has no items -->
<DataTrigger Binding="{Binding Count}" Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>

This answer assumes that you are using mvvm and have a viewmodel to back up your view - I would create a readonly bool property in your viewmodel and return true if the count of your observable collection > 0.
You can then bind the bool to the isEnabled property of the necessary text boxes, thus enabling or disabling them depending upon the value(s) of your observable collection.
EDIT - As per BenjaminPaul's comment below, the change event must be handled for your observable collection and you should then call propertyChanged on your bool property to ensure is is updated and passed back to the view.

Related

Xaml - send viewmodel property to method in another viewmodel

I have a datagrid bound to a collection of ViewModels that have a property called Distance. Is there a way in xaml to send that Distance property to a method in the ViewModel that the datagrid itemsource is on?
For example: (GetDistanceInKM would be on the same VM as the collection of reports)
<DataGrid ItemsSource="{Binding ReportViewModels}">
<DataGrid.Columns>
<DataGridTextColum Binding="{Binding Distance}" Header="Distance" EditingElementStyle="{StaticResource DistanceStyle}"/>
</DataGrid.Columns>
</DataGrid>
<Style x:Key="DistanceStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding={Binding GetDistanceInKM[Distance], Converter={StaticResource IsDistanceGreaterThanTen}} Value="True">
<Setter Property="BorderBrush" Value={StaticResource HighlightBorderBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
You cannot bind to methods, only to properties. If you want to call a method when a property changes, do it in the setter of that property.
If i understood correctly you have two options:
When Distance is set, call GetDistanceInKM and modify a new property DistanceInKM. Then bind your DataTrigger to DistanceInKM using the converter as is now.
Bind your DataTrigger directly to Distance property and make the conversion to kms in a IsDistanceGreaterThanTenKMs Converter.

Can not change template of ContentControl from style's datatrigger - datatrigger not firing

Hello WPF aficionados,
I have a content control inside a user control that sets it data context to the selected item of a listview inside the same usercontrol. The selected item property on the view model is called Selection.
Here is the content control's declaration:
<ContentControl DataContext="{Binding Selection}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource JobSnapShotProgDetail}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding bTandM}" Value="1">
<Setter Property="Template" Value="{StaticResource JobSnapShotProgDetail}"/>
</DataTrigger>
<DataTrigger Binding="{Binding bTandM}" Value="0">
<Setter Property="Template" Value="{StaticResource JobSnapShotTM_Detail}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
The Templates are defined as ControlTemplates in another resource file like:
<ControlTemplate x:Key="JobSnapShotProgDetail" >
...
</ControlTemplate >
<ControlTemplate x:Key="JobSnapShotTM_Detail" >
...
</ControlTemplate >
The binding works great meaning that the template's fields all display the correct data from the Selection object.
My Problem:
I want the content control to use one of two different control templates based on the value of the Selection's bTandM property.
What happens now:
When I change the selection of the listview, which changes the Selection object, the template IS NOT being changed based on the value of the Selection's bTandM property (a sql server bit data type). Yes the Selection object implements iNotifyPropertyChanged, and the ControlTemplate's fields that bind to the Selection's properties all display the correct data without any binding errors in the output window.
What I Tried:
It seems like the datatrigger is not getting "HIT" by the code. I tried to break the datatrigger by adding foo instead of a number which should have not been able to convert, but no error is generated. ie:
<DataTrigger Binding="{Binding bTandM}" Value="foo">
This leads me to believe that the trigger is not firing for some reason.
Question:
Can someone help me figure out why this data trigger has no effect.
Thanks in advance
JK
EDIT 1:
I tried to use a technique found HERE, but it also does not work. THe data triggers are not getting fired.
New attempt using DataTemplate with DataTriggers that target a contentcontrol's template property:
<ItemsControl ItemsSource="{Binding SelectionCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="DetailControl" Template="{DynamicResource JobSnapShotProgDetail}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding bTandM}" Value="False">
<Setter TargetName="DetailControl" Property="Template" Value="{DynamicResource JobSnapShotProgDetail}" />
</DataTrigger>
<DataTrigger Binding="{Binding bTandM}" Value="1">
<Setter TargetName="DetailControl" Property="Template" Value="{DynamicResource JobSnapShotTM_Detail}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Again, the template loads fine and the properties are display correctly for the initial template set in the style setter, but the templates do not change based on the object's bTandM boolean property.
Okay, so this turned out to be an issue with NotifyPropertyChanged.
The selection property of my viewmodel does implement INPC but the problem was that the underlying class type (vw_Job_Snapshot which is a mapped entity from a sql server view) of selection did not.
I had to implement iNPC in the underlying class and then in my viewmodel use the Property Setter for the Selection Property to also notify the specific bTandM property change like so:
Public Property Selection As vw_Job_Snapshot
Get
Return Me._Selection
End Get
Set(ByVal value As vw_Job_Snapshot)
Me._Selection = value
''Notify of specific property changed
value.OnPropertyChanged("bTandM")
_Selection = value
RaisePropertyChanged(JobSnapshotSelectedPropertyName)
End Set
End Property
After this the following code in my view worked perfectly:
<ContentControl DataContext="{Binding Selection}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{DynamicResource JobSnapShotTM_Detail}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding bTandM}" Value="False">
<Setter Property="Template" Value="{DynamicResource JobSnapShotProgDetail}"/>
</DataTrigger>
<DataTrigger Binding="{Binding bTandM}" Value="True">
<Setter Property="Template" Value="{DynamicResource JobSnapShotTM_Detail}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Thanks to everyone who helped
Try using a DataTemplateSelector instead.
Your selector would look something like this (I'm assuming your class is called JobSnapShot):
public class JobSnapShotDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is JobSnapShot)
{
JobSnapShot jobSnapShot = item as JobSnapShot;
if (jobSnapShot.bTandM == 1)
return
element.FindResource("JobSnapShotProgDetail") as DataTemplate;
else
return
element.FindResource("JobSnapShotTM_Detail") as DataTemplate;
}
return null;
}
}
XAML:
<Window.Resources>
<local:JobSnapShotDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
<ContentControl ItemTemplateSelector="{StaticResource myDataTemplateSelector}" .... />

DataTrigger does not work as expected

I have a ComboBox bound to a ViewModel property called Property.
Property is a TypeDescriptor.
When user changes the value in the ComboBox, the Property is updated.
On the UI i would like to either hide or make visible different controls: textbox, combobox, date picker etc.
Problem is, the DataTrigger is not working as expected.
<Style x:Key="textboxStyle"
TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Property.PropertyType}"
Value="{x:Type Type={x:Type sys:String}}">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
You might need to write a Converter which gets invoked when the value of 'Property' changes. The converter can be a 'TypeDescriptior to Visibility converter.
The reason why the above doesnt work is because 'PropertyType' doesnt trigger INotifyPropertyChanged.

WPF ComboBox - showing something different when no items are bound

I have a ComboBox, and i want to change its look when the ItemsSource property is null. When it is in that state, i want to show a TextPanel with the text "Retrieving data" in it, and give it a look kind of similar to the watermarked textbox.
I figure to do this i need a ControlTemplate, and a trigger. I have the ControlTemplate here:
<ControlTemplate x:Key="LoadingComboTemplate" TargetType="{x:Type ComboBox}">
<Grid>
<TextBlock x:Name="textBlock" Opacity="0.345" Text="Retrieving data..." Visibility="Hidden" />
</Grid>
<!--
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.ItemsSource" Value="0">
<Setter Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
-->
</ControlTemplate>
but my issue is how do i set up the trigger to show this when the ItemsSource property is null? I have tried a couple of different ways, and each way has given me the error message "Value 'ItemsSource' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.". My ComboBox xaml is this (including the attempted trigger):
<ComboBox Margin="112,35,80,0"
Name="MyComboBox"
Height="22.723"
VerticalAlignment="Top"
DisplayMemberPath="FriendlyName"
SelectedValuePath="Path"
TabIndex="160"
>
<Trigger>
<Condition Property="ItemsSource" Value="0" />
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</ComboBox>
now should the trigger go on the ComboBox, or on the ControlTemplate? How do i access the ComboBox's ItemsSource property? Should i even be using a trigger?
Thanks!
Try putting {x:Null} for the value of the condition instead of 0.
Also I got it working by moving the Trigger to a style and modifing it slightly, see below.
<Style TargetType="ComboBox" x:Key="LoadingComboStyle">
<Style.Triggers>
<Trigger Property="ItemsSource" Value="{x:Null}">
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<ComboBox Style="{StaticResource LoadingComboStyle}" .... >
The reason it only works in a style, is that only EventTriggers are allowed in the triggers collection directly on the Framework Element. For property triggers (like above) you need to use a style (I learn something every day).
See FrameworkElement.Triggers
Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.

What is the advantage of Properties Triggers over Data Triggers in WPF

I try to understand the advantage of Properties Triggers over Data Triggers in WPF.
It seems that Properties Triggers can be triggered only by a value that changed in dependency property, and Data Triggers can be triggered both by a value that changed in dependency property, and a value that changed in a .Net object that implement INotifyPropertyChange. So my question is, why not always use Data Triggers?
Trigger looks at properties in the item you're styling/templating, whereas DataTrigger looks at the current DataContext by default.
Example:
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- applied when mouse is over the ListBoxItem -->
</Trigger>
<DataTrigger Binding="{Binding Name}" Value="Kent">
<!-- applied when the ListBoxItem's data has a Name property set to "Kent" -->
</DataTrigger>
</Style.Triggers>
</Style>

Resources