How to make a reusable WPF Custom control with data binding properties? - wpf

If I build a custom control with some controls inside it (witch also have some bindings), how can I remove the binding parts from the custom control XAML (like Text="{Binding Path=Name}" and ItemsSource="{Binding}") to make the control reusable? My guess is to create some dependency properties but I don't know how to do this and what makes it harder for me is that some bindings are inside the DataTemplate of the custom control and I can't get the instances by GetTemplateChild().
Here is a my code:
Custom Control:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
}
Generics.xaml:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox x:Name="BindingTextBox" Text="{Binding Path=Name}"></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml:
<StackPanel>
<local:CustomListBox x:Name="BindingCustomListBox"></local:CustomListBox>
</StackPanel>
MainWindow.xaml.cs And Person(Sample Data) Class:
public partial class MainWindow : Window
{
public ObservableCollection<Person> PersonList { get; set; }
public MainWindow()
{
InitializeComponent();
PersonList = new ObservableCollection<Person>
{
new Person{ Name = "Person1" },
new Person{ Name = "Person2" }
};
BindingCustomListBox.DataContext = PersonList;
}
}
public class Person
{
public string Name { get; set; }
}
by removing binding parts I mean moving from custom control to Window.xaml or where ever the user wants to use the control.
I hope it's clear enough.

And an ItemsSource property to your control:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(CustomListBox));
}
Bind to it in your template:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{TemplateBinding ItemsSource}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox x:Name="BindingTextBox" Text="{Binding Name}"></TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...and in your view:
<local:CustomListBox x:Name="BindingCustomListBox" ItemsSource="{Binding PersonList}" />

I found a solution though not sure if a better one exists(I didn't find any after 3 days). first I added a dependency property (NameBindingStr) to enable users to define the PropertyPath of the binding:
public class CustomListBox : Control
{
static CustomListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListBox), new FrameworkPropertyMetadata(typeof(CustomListBox)));
}
public static readonly DependencyProperty NameBindingStrProperty =
DependencyProperty.Register(
"NameBindingStr", typeof(string), typeof(CustomListBox),
new FrameworkPropertyMetadata(""));
public string NameBindingStr
{
get { return (string)GetValue(NameBindingStrProperty); }
set { SetValue(NameBindingStrProperty, value); }
}
}
And the XAML for the custom control:
<Style TargetType="{x:Type local:CustomListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListBox}">
<ListBox x:Name="MainListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:BindTextBox TextBindingPath="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomListBox}}, Path=NameBindingStr, Mode=TwoWay}"></local:BindTextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
for the custom control's items to bind I inherited BindTextBox from TextBox:
public class BindTextBox : TextBox
{
static BindTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BindTextBox), new FrameworkPropertyMetadata(typeof(BindTextBox)));
}
public static readonly DependencyProperty TextBindingPathProperty =
DependencyProperty.Register(
"TextBindingPath", typeof(string), typeof(BindTextBox),
new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnTextBindingPathChanged)));
public string TextBindingPath
{
get { return (string)GetValue(TextBindingPathProperty); }
set { SetValue(TextBindingPathProperty, value); }
}
private static void OnTextBindingPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
BindTextBox elem = obj as BindTextBox;
var newTextBinding = new Binding((string)args.NewValue);
newTextBinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(elem, TextProperty, newTextBinding);
}
}
XAML:
<Style TargetType="{x:Type local:BindTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BindTextBox}">
<TextBox x:Name="TemplateTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The MainWindow.xaml.cs is not changed and I won't type it again(can be found in the question). I have to recall that my goal was to let the user to easily set the binding path. Now the the custom control can be used by one single code:
<local:CustomListBox x:Name="BindingCustomListBox" NameBindingStr="Name"></local:CustomListBox>
Works perfect.

Related

How to binding different data context to data template?

I try to simplify my Main user control that contains 8 user controls that are exactly the same but they are binding to different VM to display its data.
Currently, I have to create a template for each of my user control and binding to each of VM.
It seems that I can create one data template for all 8 user controls and apply the data template to each of the user control with different instance of VM.
Here are my code that current I have to use different templates for different dependency of View Model containing the data of each gauge
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue1VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue2VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO1Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
<Grid Background="#FFE3E2D7" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge2">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO2Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Here I try to create one Data template for all 8 user controls but it does not work
<DataTemplate x:Key="AnalogIOTemplate" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl Content="{Binding Path=GaugeValue1VMDP}" x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIOTemplate}" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Is there a way to binding different data context to the data template?
Thanks
If class of UserControl, class of VM and bindings between them are identical and the only difference is instances of VM, creating a Style for UserControl and binding each instance of VM with DataContext of corresponding instance of UserControl would be enough.
Since we don't know actual code of your UserControl and VM, I will show this by samples.
Sample UserControl which has Id dependency property and can show its value:
<UserControl x:Class="WpfApp.SampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock x:Name="IdTextBlock"/>
</StackPanel>
</UserControl>
public partial class SampleUserControl : UserControl
{
public int Id
{
get { return (int)GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
public static readonly DependencyProperty IdProperty =
DependencyProperty.Register("Id", typeof(int), typeof(SampleUserControl),
new PropertyMetadata(0, (d, e) => ((SampleUserControl)d).IdTextBlock.Text = e.NewValue.ToString()));
public SampleUserControl()
{
InitializeComponent();
}
}
Sample VM which has Id property and MainWindow's VM which has instances of sample VM:
// using Microsoft.Toolkit.Mvvm.ComponentModel;
public class SampleViewModel : ObservableObject
{
private int _id;
public int Id
{
get => _id;
set => SetProperty(ref _id, value);
}
}
public class MainWindowViewModel : ObservableObject
{
public SampleViewModel? VM1 { get; }
public SampleViewModel? VM2 { get; }
public MainWindowViewModel()
{
VM1 = new SampleViewModel { Id = 10 };
VM2 = new SampleViewModel { Id = 20 };
}
}
Finally, bind each instance of sample VM with DataContext of corresponding instance of sample UserControl so that Id of sample VM is bound with Id of sample UserControl.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow"
Width="400" Height="200">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type local:SampleUserControl}">
<Setter Property="Id" Value="{Binding Id}"/>
</Style>
</Window.Resources>
<StackPanel>
<local:SampleUserControl DataContext="{Binding VM1}"/>
<local:SampleUserControl DataContext="{Binding VM2}"/>
</StackPanel>
</Window>

Send Value from View Model to UserControl Dependency Property WPF

I have a dependency property in a UserControl with a property called SelectedColor. From my main app, the view of the window that uses this my code is:
<controls:ColorPicker SelectedColor="{Binding MyCanvas.CanvasBackgroundColor}" />
And the code from the view model is:
public MyCanvas { get; set; }
public MyWindowViewModel(MyCanvas myCanvas)
{
MyCanvas = myCanvas;
}
And then the XAML for my UserControl is:
<UserControl . . .>
<Button Click="Button_Click">
<Button.Style>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{Binding SelectedColor}" BorderBrush="Black" BorderThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</UserControl>
And the code-behind:
public ColorPicker()
{
InitializeComponent();
DataContext = this;
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(SolidColorBrush), new UIPropertyMetadata(null));
I think the problem might be with the line in the code-behind DataContext = this;. Is it correct that declaring this creates an entirely new context for the instance of this user control in the main app and therefore any values sent to it from the view model would be re-initialized? If so, how can I send the value over without it being re-declared? I also need the DataContext = this line because without it some functionality within my UserControl will no longer work.
Has anyone encountered this before?
Thanks in advance!
DataContext = this sets the DataContext of the UserControl to itself. You don't want to do this. Instead you could bind to a property of the UserControl using a {RelativeSource} without setting the DataContext property:
<Border Background="{Binding SelectedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
BorderBrush="Black" BorderThickness="1" />
Code-behind:
public ColorPicker()
{
InitializeComponent();
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(SolidColorBrush), new UIPropertyMetadata(null));

WPF - Selector - Custom Control - SelectionChanged Event Not Firing

I am new to custom control creation. I have laid some groundwork for a new custom control based on the Selector class. My understanding was that I should use this class since I needed the control to have an Items collection and the ability to handle selections. I believe that changing the ItemTemplate may have overriden some of this ability because I do not receive the SelectionChanged event at the control level or application level. I would think if I'm right that there is some sort of SelectionRegion XAML tag that I can put the DataTemplate innards into. I have not had luck in finding anything like this. After looking through Google for a while, I am ready to just ask. What am I missing? Below is the ItemTemplate markup. Thanks for any help. Thanks even more if you can tell me why the Text in TextBlock is enclosed in parentheses even though the data isn't.
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding}" Foreground="Black" Background="White" MinHeight="12" MinWidth="50"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
At the request of a commenter, here is the complete XAML for the control so far:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SourceMedicalWPFCustomControlLibrary">
<Style TargetType="{x:Type local:MultiStateSelectionGrid}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding Code}" Foreground="Black" Background="White" MinHeight="12" MinWidth="50" Padding="2" ToolTip="{Binding Description}"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MultiStateSelectionGrid}">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Content="{TemplateBinding Content}"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And the anemic code-behind as well:
namespace SourceMedicalWPFCustomControlLibrary
{
public class MultiStateSelectionGridState
{
public Brush Background { get; set; }
public Brush Foreground { get; set; }
public Brush Border { get; set; }
public string Text { get; set; }
public MultiStateSelectionGridState()
{
Background = Brushes.White;
Foreground = Brushes.Black;
Border = Brushes.Black;
Text = String.Empty;
}
};
public class MultiStateSelectionGrid : Selector
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(MultiStateSelectionGrid),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty StatesProperty =
DependencyProperty.Register("States", typeof(List<MultiStateSelectionGridState>), typeof(MultiStateSelectionGrid),
new FrameworkPropertyMetadata(new List<MultiStateSelectionGridState>(),
FrameworkPropertyMetadataOptions.AffectsRender));
public List<MultiStateSelectionGridState> States
{
get { return (List<MultiStateSelectionGridState>)GetValue(StatesProperty); }
set { SetValue(StatesProperty, value); }
}
static MultiStateSelectionGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStateSelectionGrid), new FrameworkPropertyMetadata(typeof(MultiStateSelectionGrid)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += new SelectionChangedEventHandler(MultiStateSelectionGrid_SelectionChanged);
}
void MultiStateSelectionGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("Hi");
}
}
}
here is what I do. I use the apply template function of the custom control and add a handlerto the selection chnaged event of the control I want.
simple sample here:
public event EventHandler<SelectionChangedEventArgs> YourControlSelectionChanged;
private void Selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListSelectionChanged != null) {
ListSelectionChanged(sender, e);
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//find or declare your control here, the x:name in xaml should be YourControl
YourControl== this.Template.FindName("YourControl", this) as YourControlType
YourControl.SelectionChanged += ResultListBox_SelectionChanged;
}
you can then bind to the name of the public event (YourControlSelectionChanged) you declared in your custom control class in xaml.
hope this helps.
From reading some full code examples of different controls, I believe my answer is that I am doing this all wrong. Instead, I need to have control that has a Selector like a ListBox in the ControlTemplate. THEN, #JKing 's advice would help me get to where I need to be. The answer to the actual question asked though is the aforementioned change from using Selector as a base class to having a selector in the template for the control. Thanks for the help.

Databinding to Command in Silverlight Templated Button control

I am trying to create a templated button control with databinding for the Visibility, tooltip, and Command. The Visibility binding works, as does the tooltip, but the Command does not. Another process is responsible for injecting the viewmodel and associating it with the View, and the other data bindings are working so I am pretty confident that is working properly.
In the resource dictionary:
<Converters:BoolToVisibilityConverter x:Key="boolVisibilityConverter" />
<Style TargetType="local:ImageButton">
<Setter Property="Visibility" Value="{Binding FallbackValue=Visible, Path=ToolIsAvailable, Converter={StaticResource boolVisibilityConverter} }"/>
<Setter Property="Command" Value="{Binding ButtonCommand}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid>
<Image Source="{TemplateBinding Image}"
ToolTipService.ToolTip="{Binding ToolName}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
the templated control
public class MyButton: ImageButton
{
public MyButton(MyCommandViewModel viewmodel)
{
this.DefaultStyleKey = typeof(ImageButton);
this.Image = new BitmapImage(new Uri("/MyProject;component/Themes/myimage.png", UriKind.Relative));
this.DataContext = viewmodel;
}
}
and in the view model
public MyCommandViewModel()
: base("My Tool", true)
{
}
public class CommandViewModel
{
public CommandViewModel(string toolName, bool isAvailable)
{
ToolIsAvailable = isAvailable;
ToolName = toolName;
_buttoncommand = new DelegateCommand(() =>
{
ExecuteCommand();
},
() => { return CanExecute; });
}
private bool _canExecute = true;
public bool CanExecute
{
get { return _canExecute; }
set
{
_canExecute = value;
OnPropertyChanged("CanExecute");
if (_command != null) _command.RaiseCanExecuteChanged();
}
}
private DelegateCommand _buttoncommand;
public ICommand ButtonCommand
{
get { return _buttoncommand; }
}
protected virtual void ExecuteCommand()
{
}
public bool ToolIsAvailable
{
get { return _toolIsReady; }
set { _toolIsReady = value; OnPropertyChanged("ToolIsAvailable"); }
}
public string ToolName
{
get { return _toolName; }
set { _toolName = value; OnPropertyChanged("ToolName"); }
}
}
Why are the other databindings functioning properly but not the Command data binding. I found this similar post
Overriding a templated Button's Command in WPF
Do I need to template a grid control instead and use RoutedCommands? I am not sure I understand why Silverlight treats the Command binding different than the others so I suspect I just have a bug in the code.
Does specifically looking for the datacontext work?
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ButtonCommand}"
This was my solution. Using the same commandviewmodel as above and same MyCommandViewModel
<Style TargetType="local:ImageButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid>
<Image Source="{TemplateBinding Image}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The databinding is now done in a user control
<UserControl x:Class="SilverlightApplication11.Test"
...
>
<UserControl.Resources>
<Converters:BoolToVisibilityConverter x:Key="boolVisibilityConverter" />
</UserControl.Resources>
<Grid>
<local:ImageButton Image="/SilverlightApplication11;component/Themes/hand.png" Command="{Binding ButtonCommand}" Visibility="{Binding FallbackValue=Visible, Path=ToolIsAvailable, Converter={StaticResource boolVisibilityConverter} }"/>
</Grid>
</UserControl>
and the code behind
public Test(TestCommandViewModel vm)
{
InitializeComponent();
this.Loaded += (o, e) => this.DataContext = vm;
}

multiple userControl instances in tabControl

I have a tabControl that is bound to an observable collection.
In the headerTemplate, I would like to bind to a string property, and in the contentTemplate I have placed a user-control.
Here's the code for the MainWindow.xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="contentTemplate">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="itemTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</Grid.Resources>
<TabControl IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Pages}"
ItemTemplate="{StaticResource itemTemplate}"
ContentTemplate="{StaticResource contentTemplate}"/>
</Grid>
And its code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
public class MainWindowViewModel
{
public ObservableCollection<PageViewModel> Pages { get; set; }
public MainWindowViewModel()
{
this.Pages = new ObservableCollection<PageViewModel>();
this.Pages.Add(new PageViewModel("first"));
this.Pages.Add(new PageViewModel("second"));
}
}
public class PageViewModel
{
public string Name { get; set; }
public PageViewModel(string name)
{
this.Name = name;
}
}
So the problem in this scenario (having specified an itemTemplate as well as a controlTemplate) is that I only get one instance for the user-control, where I want to have an instance for each item that is bound to.
Try this:
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Pages}">
<TabControl.Resources>
<DataTemplate x:Key="contentTemplate" x:Shared="False">
<local:UserControl1/>
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="ContentTemplate" Value="{StaticResource contentTemplate}"/>
</Style>
</TabControl.Resources>
</TabControl>
Try setting
x:Shared="False"
When set to false, modifies Windows Presentation Foundation (WPF) resource retrieval behavior such that requests for a resource will create a new instance for each request, rather than sharing the same instance for all requests.
You need to override the Equals() Method of your PageViewModel class.
public override bool Equals(object obj)
{
if (!(obj is PageViewModel)) return false;
return (obj as PageViewModel).Name == this.Name;
}
Something like this should work.
Now it is looking for the same property of the value Name. Otherwise you could also add a ID Property which is unique.

Resources