WPF ObservableCollection Item's - wpf

I have an ObservableCollection which is binded to a ListView:
private ObservableCollection<Cost> m_Costs;
public ObservableCollection<Cost> DatabaseRecords
{
get { return m_Costs; }
}
and
<ListView x:Name="ListView" ItemsSource="{Binding DatabaseRecords, UpdateSourceTrigger=PropertyChanged}">
When I add or remove item the UI will refresh, it's OK. But what if I change a property of an item in the collection? I have a DataTrigger which handle formatting items based on their property:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Status, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Value="Removed">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
If I change the Status property the UI doesn't refresh.
m_Costs[0].Status = UIStatus.Removed;
Any idea how can I utilize the binding to refresh?

For your ObservableCollection you don't need to implement INotifyPropertyChanged, cause it's already observable.
But for your Status property you need to implement it, otherwise your View will not know that property was changed.

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.

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

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.

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}" .... />

Is there a way to get the DataContext for a bound Property

Is there a way in code to get the the DataContent for the Text Property of the ComboBox defined below?
<ComboBox Height="21" Text="{Binding Path=Field1.Value}">
<ComboBox.Resources>
<Style TargetType="ComboBox">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Field2.Value}" Value="">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
</ComboBox>
Currently the DataContext of the ComboBox is the user control in which it lives. Which makes sense because I want my Text bound to one property and my DataTrigger to be bound to another property. But I need to get the DataContext that's being bound to for the Text property.
Something like this should do it:
Binding binding = BindingOperations.GetBinding(yourComboBox, ComboBox.TextProperty);
object theDataContext = binding.Source;

TreeView SelectedItem return type

I've got a TreeView that uses a HierarchicalDataTemplate and a view model as data context on different nodes. I want to access some TreeViewItem properties from TreeView.SelectedItem - but this returns a view model object not a TreeViewItem.
How to get a TreeViewItem ref to selected item?
(Ive the same problem in SelectedItemChanged handlers - object sender is a view model - how to get TreeViewItem?)
[There is a TreeView property SelectedContainer which returns a TreeViewItem but its not accessable :-( ]
This is the kind of frustrating thing about WFP is that is easy to get stuck on this kind of "detail" and it seems like there must be an easy/obvious solution but...
Once you've bound your TreeView to a data context, you will always get back view-model objects. If you want to manipulate TreeViewItem objects in response to events, you need to do it through bindings. For example, the IsExpanded, IsSelected properties can be tied to view-model properties by using styles. The following code automatically bolds the selected tree item and binds the aforementioned properties to view-model properties where I can manipulate/read them.
<TreeView x:Name="treeEquipment"
ItemsSource="{Binding RootEquipment}"
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="TreeViewItem.MouseRightButtonDown"
Handler="TreeViewItem_MouseRightButtonDown"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
The property I was trying to set was IsSelected which I always wanted false because I manage multiple selection my self. Following StrayPointers advice that works with a binding on the view mode:
class TreeNodeViewMode {
public bool no_selection {
get { return false; }
set { RaisePropertyChanged(); }
}
}
XAML:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding no_selection, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
Another approach is to handle TreeViewItem.Selected event which unlike TreeView.SelectedItemChanged (which gets a view model passed in) this does get us the TreeViewItem via:
TreeViewItem item = e.OriginalSource as TreeViewItem;
Which enables the setting of properties eg
TreeViewItem item = e.OriginalSource as TreeViewItem;
if (item != null) {
item.Focus();
item.IsSelected = false;
}

Resources