Reputation: 358
I'm trying to use a view-model-first approach and I've created a view-model for my customized chart control. Now, in my form, I want a TabControl
that will display a list of XAML-defined charts defined as such:
<coll:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
<VM:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
<VM:MyChartViewModel x:Name="ChartVM_Week" ChartType="Week" ShortName="This Week"/>
<VM:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
<VM:MyChartViewModel x:Name="ChartVM_Qtr" ChartType="Quarter" ShortName="This Quarter"/>
<VM:MyChartViewModel x:Name="ChartVM_Year" ChartType="Year" ShortName="This Year"/>
<VM:MyChartViewModel x:Name="ChartVM_Cust" ChartType="Custom" ShortName="Custom"/>
</coll:ArrayList>
Trying to specify data templates for my tab headers and content, I have this:
<DataTemplate x:Key="tab_header">
<TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="tab_content" DataType="{x:Type VM:MyChartViewModel}" >
<local:MyChartControl/>
</DataTemplate>
My TabControl
is like this:
<TabControl ItemsSource="{StaticResource ChartListTabs}"
ItemTemplate="{StaticResource tab_header}"
ContentTemplate="{StaticResource tab_content}"
IsSynchronizedWithCurrentItem="True">
<!-- nothing here :) -->
</TabControl>
What happens is that the designer shows the tabs correctly and the first tab content (can't switch tabs because they are dynamically created) showing apparently the right view for the first chart, but when I run the application, all tabs show the same, default, uninitialized content (i.e. the same chart control without any properties set). Also, the instance seems to be the same, i.e. changing something on my custom control (e.g. a date box) this shows on all tabs.
It seems to me that the control (view) in the TabControl
content stays the same (TabControl
does this, as I've read elsewhere) and should only change DataContext
when the tab changes, but it clearly doesn't.
Notes:
DependencyObject
s and my collections are ObservableCollection
s (with the exception of the ChartListTabs
resource) ShortName
is the view-model property I want to have as tab header textUpvotes: 0
Views: 97
Reputation: 358
While testing Ilan's answer I found out that when a DataContext
is declared inside the control (i.e. as an instance of some class via a UserControl.DataContext
tag), it imposes a specific object instance on the control and the ability to databind some other object to it is lost (probably because the WPF run-time uses SetData
instead of SetCurrentData
).
The recommended way to "test" your control in the designer is the d:DataContext
declaration (which works only for the designer).
Upvotes: 0
Reputation: 2782
Here is my solution used your code inside, please try to check this out.
Xaml
<Window x:Class="TabControTemplatingHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<collections:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Week" ChartType= "Week" ShortName="This Week"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Qtr" ChartType= "Quarter" ShortName="This Quarter"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Year" ChartType= "Year" ShortName="This Year"/>
<tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Cust" ChartType= "Custom" ShortName="Custom"/>
</collections:ArrayList>
<DataTemplate x:Key="TabHeader" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
<TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="TabContent" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}" >
<tabControTemplatingHelpAttempt:MyChartControl Tag="{Binding ChartType}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{StaticResource ChartListTabs}"
ItemTemplate="{StaticResource TabHeader}"
ContentTemplate="{StaticResource TabContent}"
IsSynchronizedWithCurrentItem="True"/>
</Grid></Window>
Converter code
public class ChartType2BrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = (ChartType) value;
SolidColorBrush brush;
switch (key)
{
case ChartType.Today:
brush = Brushes.Tomato;
break;
case ChartType.Week:
brush = Brushes.GreenYellow;
break;
case ChartType.Month:
brush = Brushes.Firebrick;
break;
case ChartType.Quarter:
brush = Brushes.Goldenrod;
break;
case ChartType.Year:
brush = Brushes.Teal;
break;
case ChartType.Custom:
brush = Brushes.Blue;
break;
default:
throw new ArgumentOutOfRangeException();
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Main VM
public class MyChartViewModel:BaseObservableDependencyObject
{
private ChartType _chartType;
private string _shortName;
public ChartType ChartType
{
get { return _chartType; }
set
{
_chartType = value;
OnPropertyChanged();
}
}
public string ShortName
{
get { return _shortName; }
set
{
_shortName = value;
OnPropertyChanged();
}
}
}
public enum ChartType
{
Today,
Week,
Month,
Quarter,
Year,
Custom,
}
Inner user control XAML
<UserControl x:Class="TabControTemplatingHelpAttempt.MyChartControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt">
<UserControl.Resources>
<tabControTemplatingHelpAttempt:ChartType2BrushConverter x:Key="ChartType2BrushConverterKey" />
<DataTemplate x:Key="UserContentTemplateKey" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding ChartType, Converter={StaticResource ChartType2BrushConverterKey}}"/>
<TextBlock Grid.Row="0" Text="{Binding ShortName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Grid Grid.Row="1" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ChartType, UpdateSourceTrigger=PropertyChanged}">
<Grid.DataContext>
<tabControTemplatingHelpAttempt:TabContentDataContext/>
</Grid.DataContext>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding BackgroundBrush}"/>
<TextBlock Text="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Content="{Binding }" ContentTemplate="{StaticResource UserContentTemplateKey}"/>
<!--<Grid.DataContext>
<tabControTemplatingHelpAttempt:TabContentDataContext/>
</Grid.DataContext>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Fill="{Binding BackgroundBrush}"/>
<TextBlock Text="{Binding Code, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>-->
</Grid>
Please keep in attention, that if you comment out the Grid.DataContext tag and comment in the ContentControl tag, your inner content won't be updated since it doesn't created depending on delivered MyChartViewModel. Elsewhere I can't see any problems with your code.
Inner user control VM
public class TabContentDataContext:BaseObservableObject
{
private string _code;
private Brush _backgroundBrush;
public TabContentDataContext()
{
Init();
}
private void Init()
{
var code = GetCode();
Code = code.ToString();
BackgroundBrush = code%2 == 0 ? Brushes.Red : Brushes.Blue;
}
public virtual int GetCode()
{
return GetHashCode();
}
public string Code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged();
}
}
public Brush BackgroundBrush
{
get { return _backgroundBrush; }
set
{
_backgroundBrush = value;
OnPropertyChanged();
}
}
}
Observable object code
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Update
Base Observable Dependency Object code
/// <summary>
/// dependency object that implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableDependencyObject : DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
Regards.
Upvotes: 1