WPF View sets ViewModel properties to null on closing - c#

I have an application where I'm displaying UserControls in a GroupBox. To display the controls, I'm binding to a property in the ViewModel of the main form, which returns a ViewModel to be displayed. I've got DataTemplates set up so that the form automatically knows which UserControl/View to use to display each ViewModel.
When I display a different UserControl, I keep the ViewModel of the previous control active, but the Views are discarded automatically by WPF.
The problem that I'm having is that when the view shuts down, any two way bindings to the properties in the ViewModel are immediately set to null, and so when I display the ViewModel again all of the values are just set to null in the UI.
I assume this is because as part of the View closing down it disposes and clears any values in the controls it contains, and since the bindings are in place they propagate down to the ViewModel as well.
DataTemplates in my resources
<DataTemplate DataType="{x:Type vm:HomeViewModel}">
<vw:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SettingsViewModel}">
<vw:SettingsView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:JobListViewModel}">
<vw:JobListView />
</DataTemplate>
Code used to display user controls
<GroupBox>
<ContentControl Content="{Binding Path=RightPanel}" />
</GroupBox>
Example of a control that I'm binding in one of the Views:
<ComboBox Name="SupervisorDropDown" ItemsSource="{Binding Path=Supervisors}" DisplayMemberPath="sgSupervisor"
SelectedValuePath="idSupervisor" SelectedValue="{Binding Path=SelectedSupervisorID}" />
and the relevant ViewModel properties:
public ObservableCollection<SupervisorsEntity> Supervisors
{
get
{
return supervisors;
}
}
public int? SelectedSupervisorID
{
get
{
return selectedSupervisorID;
}
set
{
selectedSupervisorID = value;
this.OnPropertyChanged("SelectedSupervisorID");
}
}
Any idea on how to stop my Views nulling the values in my ViewModels? I'm thinking that maybe I need to set the DataContext of the View to null before it closes down, but I'm not sure how to go about that with the way things are currently binding.

I've found one possible solution, but I really don't like it.
It turns out the DataContext IS being set to null already, but that doesn't help. It happens before the property is set to null. What appears to be happening is that the data bindings aren't being removed before the UserControl/View disposes of itself, and so the null value propagates down when the control is removed.
So when the DataContext changes, if the new context is null then I remove the relevant bindings on the ComboBox, as follows:
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
SupervisorDropDown.ClearValue(ComboBox.SelectedValueProperty);
}
}
I'm not a big fan of this method, because it means I have to do remember to do it for every databound control I use. If there was a way I could just have every UserControl just remove their bindings automatically when they close that would be ok, but I can't think of any way to do that.
Another option might be to just restructure my app, so that the Views don't get destroyed until the ViewModels do - this would sidestep the problem entirely.

When I display a different
UserControl, I keep the ViewModel of
the previous control active, but the
Views are discarded automatically by
WPF.
The problem that I'm having is that
when the view shuts down, any two way
bindings to the properties in the
ViewModel are immediately set to null,
and so when I display the ViewModel
again all of the values are just set
to null in the UI.
I'm no expert on either WPF or MVVM, but something about this doesn't sound right. I have trouble believing that WPF disposal of the view is causing your problem. At the very least, in my limited experience I've never had anything like that happen. I suspect the culprit is either code in the view-model or the code swaping out which view-model is used for the datacontext.

After trying to stop the null setting by various means, I gave up and instead got it working as follows. I made the ViewModel read-only before closing its view. I accomplish this in my ViewModelBase class, where I added a IsReadOnly boolean property. Then in ViewModelBase.SetProperty() (see below) I ignore any property changes when IsReadOnly is true.
protected bool SetProperty<T>( ref T backingField, T value, string propertyName )
{
var change = !IsReadOnly && !EqualityComparer<T>.Default.Equals( backingField, value );
if ( change ) {
backingField = value;
OnPropertyChanged( propertyName );
}
return change;
}
It seems to be working like this, although I'd still love to know a better solution.

I had the same problem. What worked for me was removing UpdateSourceTrigger=PropertyChanged from my SelectedValueBindings. PropertyChanged UpdateSourceTriggers seem to fire on bound properties of closing views when you use that pattern:
<!--Users DataGrid-->
<DataGrid Grid.Row="0" ItemsSource="{Binding DealsUsersViewSource.View}"
AutoGenerateColumns="False" CanUserAddRows="True" CanUserDeleteRows="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<DataGrid.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#FFC5D6FB"/>
</DataGrid.Resources>
<DataGrid.Columns>
<!--Username Column-->
<DataGridComboBoxColumn
SelectedValueBinding="{Binding Username}" Header="Username" Width="*">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding DataContext.DealsUsersCollection.ViewModels,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="SelectedValuePath" Value="Username"/>
<Setter Property="DisplayMemberPath" Value="Username"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding DataContext.BpcsUsers,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="SelectedValuePath" Value="Description"/>
<Setter Property="DisplayMemberPath" Value="Description"/>
<Setter Property="IsEditable" Value="True"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<!--Supervisor Column-->
<DataGridComboBoxColumn
SelectedValueBinding="{Binding Supervisor}" Header="Supervisor" Width="*">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding DataContext.DealsUsersCollection.ViewModels,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="SelectedValuePath" Value="Username"/>
<Setter Property="DisplayMemberPath" Value="Username"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding DataContext.BpcsUsers,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="SelectedValuePath" Value="Description"/>
<Setter Property="DisplayMemberPath" Value="Description"/>
<Setter Property="IsEditable" Value="True"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<!--Plan Moderator Column-->
<DataGridCheckBoxColumn Binding="{Binding IsPlanModerator}" Header="Plan Moderator?" Width="*"/>
<!--Planner Column-->
<DataGridCheckBoxColumn Binding="{Binding IsPlanner}" Header="Planner?" Width="*"/>
</DataGrid.Columns>
</DataGrid>
Container View:
<!--Pre-defined custom styles-->
<a:BaseView.Resources>
<DataTemplate DataType="{x:Type vm:WelcomeTabViewModel}">
<uc:WelcomeTabView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:UserSecurityViewModel}">
<uc:UserSecurityView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PackItemRegisterViewModel}">
<uc:PackItemsRegisterView/>
</DataTemplate>
</a:BaseView.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<TabPanel Grid.Column="1" Grid.Row="1">
<TabControl TabStripPlacement="Top" ItemsSource="{Binding TabCollection}" SelectedIndex="{Binding SelectedTabIndex}"
DisplayMemberPath="DisplayName" MinWidth="640" MinHeight="480"/>
</TabPanel>
</Grid>
Container ViewModel:
TabCollection.Add(new WelcomeTabViewModel());
TabCollection.Add(new UserSecurityViewModel(_userService, _bpcsUsersLookup));
TabCollection.Add(new PackItemRegisterViewModel(_packItemService, _itemClassLookup));
SelectedTabIndex = 0;

Set the UpdateSourceTrigger explicit to LostFocus
If the view is closing and sets its data to null, it has no effect on your data in the viewmodel.
<ComboBox Name="SupervisorDropDown" ItemsSource="{Binding Path=Supervisors}" DisplayMemberPath="sgSupervisor"
SelectedValuePath="idSupervisor"
SelectedValue="{Binding Path=SelectedSupervisorID, UpdateSourceTrigger=LostFocus}" />

Related

In WPF, is it possible to represent the same ViewModel with different Views, based on some condition?

I am making a TreeView with multiple levels of information being displayed. What I am hoping to achieve, is to show a different view when the sub-tree is expanded and closed. So, when just looking at the list, I want to make a view which shows a brief status overview of my more detailed view that would be visible if you expanded that item in the list.
I know I could just create 2 viewmodels implementing the same Interface, and based on a boolean IsExpanded, I could set the ActiveViewModel to one or the other, but I was just curious if I could just have the one viewmodel and change its view based on that boolean instead to save memory.
-OR-
Alternatively, should I just put 2 StackPanels into the same View, and then bind a visibility to be inverse of each other, so only one can be shown at a time?
-CODE-
Here is my current code (Private information removed / generic representation):
Xaml:
<UserControl.Resources>
<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Padding" Value="5"></Setter>
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{StaticResource Univers57}"/>
<Setter Property="FontSize" Value="20" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</UserControl.Resources>
<TreeView x:Name="TreeView"
HorizontalAlignment="Stretch"
ItemsSource="{Binding ItemViewModels}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type viewModel:ItemViewModel}" ItemsSource="{Binding Tasks}">
<StackPanel Orientation="Horizontal" Visibility="{Binding IsVisible}>
<Image Source="../../Images/Image.png" Height="24" Width="24"/>
<TextBlock Text="{Binding Item.Name}" />
<Button Style="{StaticResource ButtonStyle}">Dispatch</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" Visibility="{Binding IsOtherVisible}>
<Image Source="../../Images/Image2.png" Height="24" Width="24"/>
<TextBlock Text="{Binding Item.Name}" />
<Button Style="{StaticResource ButtonStyle}">Dispatch</Button>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type viewModel:TaskViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Task.Name}" Width="200"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
Both the ItemViewModel and TaskViewModel inherit from 'TreeViewModel' which implements INotifyPropertyChanged, and has IsExpanded and IsSelected.
IsVisible on the StackPanel Binding should be set based on IsExpanded's value. (It only shows up once you expand the item. So, one stackpanel or the other should show up).
I have just had a play with the WPF Visual Tree tools in VS2015 and it looks like the IsExpanded isnt being changed when I expand/collapse the tree items. It only sets a value during creation of the viewmodels, after that it will never change - even though they physically open and close when running the program.
Have managed to find a solution.
Firstly, needed to set 2 Way Binding on the binding to IsExpanded.
Secondly, I have overridden / hidden IsExpanded by declaring it as new in the derived class.
Now when set() gets called on IsExpanded, I can make a change to the IsVisible and IsOtherVisible before sending OnPropertyChange(); Now that I can change the IsVisible's, they can fire their own OnPropertyChange() and all is good in the world again.

Passing View as Command Parameter in WPF.Does it violet MVVM approach?

I have data grid and there some number of columns in it.There are number of rows. I want to show one window when user clicks on the context menu of that row.I need first columns value in viewmodel from that row for some logic.Currently I am passing placement target as command parameter i.e.gridviewrow. Following is my code
<telerik:RadGridView.RowStyle>
<Style TargetType="telerik:GridViewRow">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" ></Setter>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="show Window" Command="{Binding PlacementTarget.Tag.DataContext.ShowChart,RelativeSource={RelativeSource AncestorType=ContextMenu, Mode=FindAncestor}}" CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</telerik:RadGridView.RowStyle>
How can I pass value of first column of particular row on which user has clicked?
Does it violet MVVM approach?What is solution if it violets MVVM approach in this case?
Teoretically yes, because the only way to access the view is by INotifyPropertyChanged and IErrorDataInfo. However, it depends what do you inttend to do. If you want to change the visibilty of a UI elemnt, I violet the MVVM pattern because the other way arround it seems to complicated to me. I suggest to tell what exactly do you want to do, and maybe I will be able to help you :)
Maybe You can try to create a new ViewModel class for the grid's rows and each column is a property of the ViewModel. So you can easily bind the data context of the row to that ViewModel and the columns to its properties.
I will post another answer since you edited masively the question.
If you use a datagrid in your view, the your view (XAML code) should look like this:
<Window x:Class="CareerTrackWpfClient.Views.User_Main_Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="User_Main_Window" Background="Black">
<Window>
<Grid HorizontalAlignment="Center" Visibility="Collapsed" Name="gridbooks">
<DataGrid Height="650" HorizontalAlignment="Stretch" Name="BooksGrid" RowHeight="90" ColumnWidth="200"
ColumnHeaderHeight="40" HeadersVisibility="Column" Background="Transparent" RowBackground="DarkGray"
AlternatingRowBackground="LightBlue" BorderBrush="Gray"
BorderThickness="2" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Book #" Width="220" Binding="{Binding BookID}" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Book Title" Width="220" Binding="{Binding Title}" >
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
You should have a model like this
public class Book
{
public int BookID {get; set;}
public string Title {get;set;}
}
Now, either you do a ViewModel where you fill the list (like Zarzan said) - and this way you will respect the MVVM pattern. Or in the code beheind, in the constructor, bellow the InitializeComponent() method, you write something like this
List<Book> booksProvider=new List<Book>();
booksProvider.Add(new Book{BookID=1,"Book 1"}) ;
booksProvider.Add(new Book{BookID=2,"Book 2"}) ;
gridbooks.ItemsSource=booksProvider;
Is very nice using this UI component (DataGrid), from my point of view. It has the validations on place. Try to enter strings on the BookID field, and it will notify you. It is very flexible and you don't have to install anything.
Hope it helps as many developers as possible.

Datagrid and Listview with same itemssource

I have a WPF application with a DataGrid and ListView that share the same ObservableCollection ItemsSource. When the DataGrid's CanUserAddRows property is True it causes the ListView to display the extra item that the DataGrid uses to add new rows.
How can I get the extra row from the DataGrid to not show in the ListView?
I tried using a trigger on the ListView's DataTemplate and checking if the items Id was empty or 0
`<ListView.ItemTemplate>
<DataTemplate>
<Label Margin="-2,0,0,0" Name="CategoryLabel" >
<TextBlock TextWrapping="Wrap" Text="{Binding categoryName}" Height="46"></TextBlock>
</Label>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding categoryId}" Value="0" > <!-- also tried Value="" -->
<Setter TargetName="CategoryLabel" Property="Visibility" Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>`
I just posted an answer to a problem of changing the template using a data template selector
Change View with its ViewModel based on a ViewModel Property
Possibly just because I have recently looked at this but I wonder if it might be possible to use the same technique here.
Have one template for where the category has a value,then another blank template for values without a category. The important part is you do the test in code rather than XAML so easier to inspect.
You can solve your problem without any modification of your ViewModel or code behind. You can do well without explicitly defining CollectionView's of any kind. Just add to your view's XAML one more (or only) DataTrigger that triggers on the NewItemPlaceholder item of the default view of ListView ItemsSource's collection. Have this trigger to set the UIElement.Visibility attached property to "Hidden". Place it within ItemContainerStyle style triggers. Like this:
<ListView
ItemsSource="{Binding ...}"
>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
...
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding}"
Value="{x:Static CollectionView.NewItemPlaceholder}">
<Setter Property="UIElement.Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="..." Value="{Binding ...}" />
...
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label Margin="..." Name="...">
<TextBlock TextWrapping="Wrap"
Text="{Binding ...}" />
</Label>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>

How to access control inside of a ControlTemplate

How can I access my btnViewTable from code-behind? Specifically to be able to set the visibility on or off, or even remove it. I've looked at GetTemplateChild as well as FindName, but have been unable to access the button. I can manage to obtain a reference to the ControlTemplate, but can't get any further than that.
<Grid x:Name="pnlSearch" Background="White">
<TextBlock x:Name="txtSearchResults" />
<sdk:DataGrid x:Name="grdResults">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding Value}"/>
<sdk:DataGridTextColumn Binding="{Binding FoundFieldName}"/>
</sdk:DataGrid.Columns>
<sdk:DataGrid.RowGroupHeaderStyles>
<Style TargetType="sdk:DataGridRowGroupHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="sdk:DataGridRowGroupHeader">
<sdk:DataGridFrozenGrid Name="Root">
<StackPanel>
<Button x:Name="btnViewTable"
var button = (Button)DataGrid.Template.FindName("btnViewTable", "DataGridControl");
button.Click += //Do something;
Where DataGrid/DataGridControl is the actual DataGridControl
To handle parts of the template, from outside of the control, is always a not-so-good idea.
For a quick fix, I'd go with:
<Style TargetType="sdk:DataGridRowGroupHeader">
<Setter Property="Tag" Value="{Binding SomeVisibilityProperty}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="sdk:DataGridRowGroupHeader">
<Grid
<sdk:DataGridFrozenGrid Name="Root">
<StackPanel>
<Button x:Name="btnViewTable"
Visibility="{TemplateBinding Tag}"
Where SomeVisibilityProperty is a property in the data context. Might need to use RelativeSource.
For a "true" fix, I'd define an attached property. Set this property to bind to the same property on parent grid and then have the button to bind to the property on DataGridRowGroupHeader.
I ended up just referring to a property in codebehind:
http://www.jayway.com/2011/05/17/bind-from-xaml-to-property-defined-in-code-behind/

DataTemplate in ControlTemplate not updating Binding

I've created a control with 3 PART_s, one PART_ changes depending on the type bound to it, however values changed within the Control do not update the Binding, it seems to work as OneWay Binding.
Here's part of the code I beleive is relevant:
<DataTemplate x:Key="BooleanDAView" DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="DateTimeDAView" DataType="{x:Type sys:DateTime}">
<extToolkit:DateTimePicker Value="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="Int32DAView" DataType="{x:Type sys:Int32}">
<extToolkit:IntegerUpDown Value="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate x:Key="StringDAView" DataType="{x:Type sys:String}">
<TextBox Text="{Binding ., Mode=TwoWay}"/>
</DataTemplate>
....
<ContentControl x:Name="PART_Content"
Grid.Row="0" Grid.Column="1"
Margin="{TemplateBinding Padding}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Content="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
>
<ContentControl.ContentTemplateSelector>
<controls:TypeBasedDataTemplateSelector>
<controls:TypeBasedDataTemplateSelector.Templates>
<controls:TypedDictionary>
<sys:String x:Key="{x:Type sys:Boolean}">BooleanDAView</sys:String>
<sys:String x:Key="{x:Type sys:DateTime}">DateTimeDAView</sys:String>
<sys:String x:Key="{x:Type sys:Int32}">Int32DAView</sys:String>
<sys:String x:Key="{x:Type sys:String}">StringDAView</sys:String>
</controls:TypedDictionary>
</controls:TypeBasedDataTemplateSelector.Templates>
</controls:TypeBasedDataTemplateSelector>
</ContentControl.ContentTemplateSelector>
</ContentControl>
For Content I've also tried ... RelativeSource={RelativeSource AncestorType=local:DABaseControl} but no change.
If the DataTemplate Binding use "{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" the template doesn't change once set.
Or is there a better way to do this?
Thanks
I just encountered the same problem, I wanted to create a DataTemplate with DataType="{x:Type sys:Boolean} that just had a checkbox. But there were many warning signs along the way telling me this isn't the way it should be done.
At first, the simple binding of {Binding} would throw an exception "Two-way binding requires path or xpath", which was the first warning sign. I changed the binding to {Binding .} which worked (even though this MSDN article clearly states that they're equivalent). That fact that voodoo was helping was the second warning sign. It then displayed correctly and the checked state was according to the boolean value, but when clicking the checkbox (even with UpdateSourceTrigger=PropertyChanged), it refused to update the binding source, no matter what I tried. Using diagnostics:PresentationTraceSources.TraceLevel=High showed that it didn't even try to bind back (third warning sign).
I went ahead and created a simple "box" for the bool value - a class with a single bool property named Value with anINotifyPropertyChanged implementation. I changed the binding to {Binding Value} and now everything worked, including two way binding.
Conclusion: It seems a binding can't update the bound object itself, but only properties of that object (which is why {Binding} throws an exception, but the more explicit {Binding .} suppresses that exception, according to H.B.'s answer). In any case, the approach of creating a ViewModel and creating templates that target it appears to be more than a mere design guideline, but an actual technical requirement.
I've actually never worked with a ContentTemplateSelector, but if I had to hazard a guess I would say either it's not responding to PropertyChanged events on your ContentControl.Content property, or your Content binding is incorrect.
You can easily check if your binding is correct or not by removing the ContentTemplateSelector and seeing if data shows up at all. If it does, your binding is correct. If it doesn't, it's incorrect and you need to fix it.
If the problem is the ContentTemplateSelector, then I would suggest switching to a DataTrigger which determines which ContentTemplate to use based on the Content. This is what I usually do, and it uses a Converter which simply returns typeof(value)
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource StringDAView}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type sys:Boolean">
<Setter Property="ContentTemplate" Value="{StaticResource BooleanDAView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type DateTime">
<Setter Property="ContentTemplate" Value="{StaticResource DateTimeDAView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Converter={StaticResource ObjectToTypeConverter}"
Value="{x:Type sys:Int32">
<Setter Property="ContentTemplate" Value="{StaticResource Int32DAView}" />
</DataTrigger>
</Style.Triggers>
</Style>

Categories