Why does ComboBox not initially show the ComboBoxItem with IsSelected==true? - c#

In the following code, why doesn't the item with property IsSelected set to true become selected in the ComboBox just as it does in the ListBox after clicking the Button?
Once I click on the ComboBox, the selected item becomes selected, but not before.
xaml:
<Window x:Class="WpfApplication1.Desktop.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<StackPanel>
<ListBox ItemsSource="{Binding Items}">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Txt}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Txt}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Select second item" Click="Button_Click" />
</StackPanel>
</Window>
xaml.cs:
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Windows;
using Microsoft.Practices.Prism.ViewModel;
namespace WpfApplication1.Desktop
{
[Export]
public partial class Shell : Window
{
public class Foo : NotificationObject
{
static int _seq = 0;
string _txt = "Item " + (++_seq).ToString();
public string Txt { get { return _txt; } }
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
RaisePropertyChanged(() => IsSelected);
}
}
}
public ObservableCollection<Foo> Items { get; set; }
public Shell()
{
Items = new ObservableCollection<Foo>();
for (int i = 0; i < 5; i++)
Items.Add(new Foo());
DataContext = this;
InitializeComponent();
}
void Button_Click(object sender, RoutedEventArgs e)
{
Items[1].IsSelected = true;
}
}
}

It's because the ItemContainerStyle is applied only when the ComboBoxItems are generated (i.e. when you open the dropdown).
To work around this, you create another property called SelectedItem and bind the Combobox's SelectedValue to it.
Long explanation and example here

Because the binding is set on UpdateSourceTrigger=LostFocus by default, you would have to change it to PropertyChanged to get the result you want.
Like this:
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"Value="{Binding Path=IsSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</Style>

When using a Model as the DataContext for a WPF Window, the Controls may not initially behave as you'd expect. Essentially, some properties/events never get set/called until after the Window been initialized. The work-around in this case is to setup the binding in the Window's Loaded event.
Disclaimer: I have not tested this with the OP's specific scenario, but this is the behavior and work-around I've encountered in the past.

Related

How do I use a window-level databound style in a DataTemplate

In a WPF project, my viewmodel has some general properties that I bind to a style. I then would like to use that style in a DataTemplate where I bind a collection from my viewmodel.
The databound style works outside the DataTemplate as expected, but does not apply inside. When debugging I can see that it is looking for the general properties inside the collection objects, so my question is, how do I inside a DataTemplate get a hold of properties from the viewmodel. I imagine I have to use a RelativeSource binding, but I have not been able to get it working.
This quick app should show what I am trying to do:
MainWindow.xaml
<Window x:Class="StyleTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StyleTest"
mc:Ignorable="d"
Title="Test"
SizeToContent="WidthAndHeight">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="Header">
<Setter Property="FontSize" Value="{Binding FontSize}" />
<Setter Property="Foreground" Value="{Binding Foreground}" />
</Style>
<DataTemplate x:Key="UserTemplate">
<StackPanel>
<TextBlock Style="{StaticResource Header}" Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid Margin="20">
<StackPanel>
<ItemsControl Name="Itemscontrol" ItemsSource="{Binding Users}" ItemTemplate="{StaticResource UserTemplate}" />
<TextBlock Style="{StaticResource Header}">Style this.</TextBlock>
</StackPanel>
</Grid>
</Window>
MainWindow.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
namespace StyleTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Model m = new Model {
FontSize = 28,
Foreground = new SolidColorBrush(Colors.Orange),
Users = new List<User>() };
m.Users.Add(new User() { Name = "Mambo No. 1" });
m.Users.Add(new User() { Name = "Right Hand Rob" });
m.Users.Add(new User() { Name = "Perry Junior" });
this.DataContext = m;
}
}
public class Model
{
private int fontSize;
public int FontSize { get => fontSize; set => fontSize = value; }
private SolidColorBrush foreground;
public SolidColorBrush Foreground { get => foreground; set => foreground = value; }
private List<User> users;
public List<User> Users { get => users; set => users = value; }
}
public class User
{
public string Name { get; set; }
}
}
I think you want something like this:
<Style TargetType="TextBlock" x:Key="Header">
<Setter Property="FontSize" Value="{Binding Path=DataContext.FontSize, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
<Setter Property="Foreground" Value="{Binding Path=DataContext.Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</Style>

Binding from a property of each item inside an ItemsControl to a property of an object outside the ItemsControl

I have an ItemsControl, and a Button outside the ItemsControl. Each item inside the ItemsControl has a dependency property called "MyProperty" (defined in the code-behind).
I would like to set the IsEnabled property of the Button to false when at least one of the items in the ItemsControl has the MyProperty property set to 5. (of course this is just a stupid example of a more complicated situation)
I tried by means of a data trigger, but with no luck:
XAML:
<Window x:Class="cancellami24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="MyStyle" TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyProperty}" Value="5">
<Setter Property="IsEnabled" TargetName="MyButton" Value="False" /><!--error on TargetName-->
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ItemsControl x:Name="MyListBox" Grid.Row="0" ItemContainerStyle="{StaticResource MyStyle}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyProperty, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button x:Name="MyButton" Grid.Row="1" Click="MyButton_Click"/>
</Grid>
</Window>
Code-behind:
using System.Collections.ObjectModel;
using System.Windows;
namespace cancellami24
{
public partial class MainWindow : Window
{
private readonly ObservableCollection<MyItem> myCollection = new ObservableCollection<MyItem>();
public MainWindow()
{
InitializeComponent();
myCollection.Add(new MyItem(1));
myCollection.Add(new MyItem(2));
myCollection.Add(new MyItem(3));
MyListBox.ItemsSource = myCollection;
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
myCollection[2].SetValue(MyItem.MyPropertyProperty, 5);
}
}
public class MyItem : DependencyObject
{
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(int), typeof(MyItem));
public MyItem(int propertyValue)
{
SetValue(MyPropertyProperty, propertyValue);
}
}
}
You need custom converter to solve it
public class MyConverter : IValueConverter
{
bool flag = false;
var collection = value as ObservableCollection<MyItem>();
if(collection==null) return flag;
foreach (var item in collection)
{
if (item.MyProperty==5)
{
flag = true;
break;
}
}
return flag;
}
Add MyConverter to your App.xaml
<local:MyConverter x:key="MyConverter"/>
Xaml:
<Button x:Name="MyButton" IsEnabled="{Binding ElementName=MyListBox, Path=ItemsSource, Converter={StaticResource MyConverter}}"/>

Define different DataTemplate for same source

I'm trying to show collection of bool with a DataTemplate for ListView.
Here's the code:
In MainWindow.xaml
<Window.Resources>
<DataTemplate x:Key="ListItemCheckBoxTemplate">
<CheckBox IsChecked="{Binding Mode=OneWay}"/>
</DataTemplate>
<DataTemplate x:Key="ListItemRadioButtonTemplate">
<RadioButton IsChecked="{Binding Mode=OneWay}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="ListViewSample" />
<ListBox ItemsSource="{Binding MyData}" ItemTemplate="{StaticResource ListItemCheckBoxTemplate}" Grid.Column="1"/>
</Grid>
In MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
private List<bool> myBooleanCollection;
public List<bool> MyData
{
get { return myBooleanCollection; }
set { myBooleanCollection = value; }
}
public ViewModel()
{
myBooleanCollection = new List<bool>();
myBooleanCollection.Add(true);
myBooleanCollection.Add(false);
myBooleanCollection.Add(true);
myBooleanCollection.Add(true);
myBooleanCollection.Add(false);
}
}
Instead of ItemTemplate ListItemCheckBoxTemplate I want to apply ListItemRadioButtonTemplate with Radio button. How I can specify the use of ItemTemplate for the same source in xaml. Do I need to change the DataSource for the same or
do I have some way to specify the DataTemplate in xaml based on condition.
You did not specify what is the condition on which you want to change template but you can add another property to your view model, say AllowMultiple:
public class ViewModel: INotifyPropertyChanged
{
private List<bool> myBooleanCollection;
public List<bool> MyData
{
get { return myBooleanCollection; }
set { myBooleanCollection = value; }
}
private bool _allowMultiple;
public bool AllowMultiple
{
get { return _allowMultiple; }
set
{
if (_allowMultiple != value)
{
_allowMultiple = value;
OnPropertyChanged("AllowMultiple");
}
}
}
}
and then change ListBox.Style to:
<ListBox ItemsSource="{Binding MyData}">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate" Value="{StaticResource ListItemCheckBoxTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=AllowMultiple}" Value="False">
<Setter Property="ItemTemplate" Value="{StaticResource ListItemRadioButtonTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
assuming that ViewModel implements INotifyPropertyChanged you can then change ItemTemplate buy changing AllowMultiple against view model
You can specify DataTemplate for each item with the followign attribute:
ItemTemplate="{StaticResource ListItemCheckBoxTemplate}
Change this to:
ItemTemplate="{StaticResource ListItemRadioButtonTemplate}
If you need multiple item templates in the same ListBox then you can provide custom template selection logic via the DataTemplateSelector Class
If you want to do it in Xaml, you will need to expose property which you can bind to as a selector. Have a look at A Data Template Selector in Xaml
You need create DataTemplateSelector and using this for ItemTemplateSelector. You also need to determine the condition under which you will return the DataTemplate. Example:
XAML
<Window x:Class="YourNamespace.MainWindow" ...
xmlns:this="clr-namespace:YourNamespace"
<Window.Resources>
<DataTemplate x:Key="OneTemplate">
<CheckBox IsChecked="{Binding Mode=OneWay}" />
</DataTemplate>
<DataTemplate x:Key="SecondTemplate">
<RadioButton IsChecked="{Binding Mode=OneWay}" />
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{Binding MyData}">
<ListBox.ItemTemplateSelector>
<this:MyItemTemplateSelector OneTemplate="{StaticResource OneTemplate}"
SecondTemplate="{StaticResource SecondTemplate}" />
</ListBox.ItemTemplateSelector>
</ListBox>
Code-behind
public class MyItemTemplateSelector : DataTemplateSelector
{
public DataTemplate OneTemplate { get; set; }
public DataTemplate SecondTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
bool myItem = (bool)item;
if (myItem == true)
{
return OneTemplate;
}
return SecondTemplate;
}
}

Binding SelectedItem in nested ListBox using Caliburn.Micro

I'm trying to use Caliburn.Micro to bind a view model of a nested ListBox but I'm stuck.
I have a list workspace that is divided in two sections a list of groups containtaining items in a different part of that workspace I want to show the details of either a group or an item in a group depending on what is the SelectedItem. I'm new to Caliburn.Micro and looked at the documentation and samples but don't know how to connect the dots. Specifically I'm trying to model this after the Caliburn.Micro.HelloScreens sample. The code I have so far:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : Conductor<AnalyzerGroupWorkspaceViewModel>, IWorkspace
{
private Selected selected = Selected.AnalyzerGroup;
private const string name = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(analyzerGroups.GetAll().Select(fromMapper.Map<Model.AnalyzerGroup,AnalyzerGroupViewModel>));
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return name; } }
public Selected Selected
{
get { return selected; }
set
{
if (value == selected) return;
selected = value;
NotifyOfPropertyChange(() => Selected);
}
}
private IConductor Conductor { get { return (IConductor) Parent; } }
public void Show()
{
var haveActive = Parent as IHaveActiveItem;
if (haveActive != null && haveActive.ActiveItem == this)
{
DisplayName = name;
Selected = Selected.AnalyzerGroup;
}
else
{
Conductor.ActivateItem(this);
}
}
}
The view:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox x:Name="AnalyzerGroups">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" />
<ListBox x:Name="Analyzers">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id }"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Context="{Binding Selected, Mode=TwoWay}"
cal:View.Model="{Binding}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
</GroupBox>
</DockPanel>
</UserControl>
Next to that I have two UserControls that display the detailsof a group or item.
My specific question is how can I use the SelectedItem property of the two ListBoxes to modify the Selected property to switch between displaying the AnalyzerGroup details and the Analyzer details?
I've found the solution to the above described problem the solution consists of four parts:
Add a IsSelected property (that notifies changes) to both 'child' ViewModels
Bind the IsSelected property of the ListBox.ItemContainerStyle to the IsSelected property of the respective ViewModels
Attach a Caliburn.Micro Message to the 'outer' ListBox and use the $eventArgs argument
In the ViewModel bound to the entire UserControl implement the method corresponding to the Message and use the AddedItems property of the eventArgs to set the SelectedViewModel property setting the IsSelected property of the previous SelectedViewModel to false
The code then becomes:
The ViewModel:
public class AnalyzerGroupWorkspaceViewModel : PropertyChangedBase, IAnalyzerGroupWorkspaceViewModel
{
private IAnalyzerViewModel selectedViewModel;
private const string WorkspaceName = "Analyzers";
public AnalyzerGroupWorkspaceViewModel(
IMappingEngine fromMapper,
IRepository<Model.AnalyzerGroup> analyzerGroups)
{
AnalyzerGroups = new ObservableCollection<IAnalyzerGroupViewModel>(
analyzerGroups.GetAll().Select(
fromMapper.Map<Model.AnalyzerGroup, AnalyzerGroupViewModel>));
}
public void SelectionChanged(object eventArgs)
{
var typedEventArgs = eventArgs as SelectionChangedEventArgs;
if (typedEventArgs != null)
{
if (typedEventArgs.AddedItems.Count > 0)
{
var item = typedEventArgs.AddedItems[0];
var itemAsGroup = item as IAnalyzerViewModel;
if (itemAsGroup != null)
{
SelectedViewModel = itemAsGroup;
}
}
}
}
public ObservableCollection<IAnalyzerGroupViewModel> AnalyzerGroups { get; private set; }
public string Name { get { return WorkspaceName; } }
public IAnalyzerViewModel SelectedViewModel
{
get { return selectedViewModel; }
set
{
if (Equals(value, selectedViewModel))
{
return;
}
if (SelectedViewModel != null)
{
SelectedViewModel.IsSelected = false;
}
selectedViewModel = value;
NotifyOfPropertyChange(() => SelectedViewModel);
}
}
}
The View:
<UserControl x:Class="Philips.HHDx.SSW.AnalyzerGroup.AnalyzerGroupWorkspaceView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org">
<DockPanel>
<GroupBox Header="AnalyzerGroups" DockPanel.Dock="Top">
<ListBox SelectionMode="Single"
x:Name="AnalyzerGroups"
cal:Message.Attach="[Event SelectionChanged] = [Action SelectionChanged($eventArgs)]">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="2" BorderBrush="DarkGray">
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock Text="{Binding Name}" />
<ListBox SelectionMode="Single" ItemsSource="{Binding Analyzers}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="DarkGray">
<StackPanel>
<TextBlock Text="{Binding Name }" Margin="10" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
<GroupBox Header="Details">
<ContentControl cal:View.Model="{Binding SelectedViewModel}" />
</GroupBox>
</DockPanel>
</UserControl>
The answer to your specific question is yes, you can.
On the ViewModel of your UserControl. You create a property that is a ViewModel of either of the two details.
public interface IAnalyzerViewModel
{
}
Next, create two ViewModels for the Views of your Analyzer and AnalyzerGroup views.
public class AnalyzerGroupViewModel : IAnalyzerViewModel
{
}
public class AnalyzerViewModel : IAnalyzerViewModel
{
}
Next, create a property in your UserControl's ViewModel that implements INPC or PropertyChangedBase of Caliburn Micro.
public class MainViewModel :
{
private IAnalyzerViewModel _analyzerViewModel;
public IAnalyzerViewModel SelectedViewModel { get { return _analyzerViewModel; } set { _analyzerViewModel = value; OnPropertyChanged(() => SelectedViewModel); }
//Hook up the selected item changed event of your listbox and set the appropriate ViewModel to show, so if you either want to show the AnalyzerGroup or AnalyzerView.
}
And lastly, just update your MainView to
<ContentControl x:Name="SelectedViewModel"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"/>
Caliburn will hook up the appropriate bindings and stuff and will pull the View for the associated ViewModel, and also the Name convention part will automatically map it to any public property of its datacontext as long as the names match.

WPF ComboBox selection change after switching tabs

I made a project based on nested tabs.
the nested tabs are different instance of the same viemModel and the same UI.
when I switch between the tabs he comboboxes present in the tabs chenge thei selection depending on the tab that is loosing focus.
I add both the viewmodels and the view of my test project.
thank you in advance for your help
main window
<Window.Resources>
<DataTemplate DataType="{x:Type local:IntermediateViewModel}">
<local:IntermediateView />
</DataTemplate>
<DataTemplate x:Key="HeaderedTabItemTemplate">
<Grid>
<ContentPresenter
Content="{Binding Path=Header, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" >
</ContentPresenter>
</Grid>
</DataTemplate>
<Style x:Key="SimpleTabItemStyle" TargetType="TabItem">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" BorderThickness="1" BorderBrush="#555959">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center"
ContentSource="Header" Margin="12,2,12,2" RecognizesAccessKey="True" Height ="40" MinWidth ="90"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#555959" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="DefaultTabControlTemplate">
<TabControl IsSynchronizedWithCurrentItem="True"
BorderThickness="0"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource HeaderedTabItemTemplate}"
ItemContainerStyle="{StaticResource SimpleTabItemStyle}"
SelectionChanged="TabControl_SelectionChanged"
/>
</DataTemplate>
<!---->
</Window.Resources>
<Grid MinHeight="200" MinWidth="300">
<Grid.RowDefinitions>
<RowDefinition Height="260*" />
<RowDefinition Height="51*" />
</Grid.RowDefinitions>
<Border >
<ContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{DynamicResource DefaultTabControlTemplate}"
/>
</Border>
<Button Grid.Row="1" Content="Add" Command="{Binding AddCommand}"/>
</Grid>
view model (create a different istance each time)
class MainWindowViewModel : WorkspacesViewModel<IntermediateViewModel>
{
public MainWindowViewModel()
{
this.WorkspacesView.CurrentChanged += new EventHandler(WorkspacesView_CurrentChanged);
}
void WorkspacesView_CurrentChanged(object sender, EventArgs e)
{
}
RelayCommand myVar = null;
public ICommand AddCommand
{
get
{
return myVar ?? (myVar = new RelayCommand(param =>
{
SetWindow(new IntermediateViewModel("AA" + this.Workspaces.Count) );
}));
}
}
first level tab
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:ClassViewModel}">
<local:ClassView />
</DataTemplate>
</UserControl.Resources>
<Border>
<ContentControl Content="{Binding Path=CurrentWorkspace, Mode=OneWay}" Loaded="ContentControl_Loaded" DataContextChanged="ContentControl_DataContextChanged" IsVisibleChanged="ContentControl_IsVisibleChanged" LayoutUpdated="ContentControl_LayoutUpdated" TargetUpdated="ContentControl_TargetUpdated" Unloaded="ContentControl_Unloaded" />
</Border>
first level viewmodel
class IntermediateViewModel : WorkspacesViewModel
{
public string Header { get; set; }
public IntermediateViewModel(string header)
{
Header = header;
SetWindow(new ClassViewModel(header));
}
}
nested tab
<UserControl.Resources>
<CollectionViewSource x:Key="StatusView" Source="{Binding Path=StatusList}"/>
</UserControl.Resources>
<Grid>
<ComboBox Name="_spl2Status" ItemsSource="{Binding Source={StaticResource StatusView}}"
SelectedValue="{Binding Path=MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="FL_TYPE"
DisplayMemberPath="ID_TYPE" Margin="76,12,0,0" Height="40" VerticalAlignment="Top" HorizontalAlignment="Left" Width="146"
DataContextChanged="_spl2Status_DataContextChanged"
IsVisibleChanged="_spl2Status_IsVisibleChanged"
Loaded="_spl2Status_Loaded"
SelectionChanged="_spl2Status_SelectionChanged"
>
</ComboBox>
</Grid>
nested tab view model
public enum myTypes
{
tipo0 = 0,
tipo1 = 1,
tipo2 = 2,
}
class ClassViewModel : WorkspaceViewModel
{
public ClassViewModel(string name)
{
Name = name;
}
public string Name { get; set; }
private List<IntEnumType> _statusList = null;
public List<IntEnumType> StatusList
{
get
{
if (_statusList == null)
_statusList = new List<IntEnumType>()
{
new IntEnumType((int)myTypes.tipo0, myTypes.tipo0.ToString()),
new IntEnumType((int)myTypes.tipo1, myTypes.tipo1.ToString()),
new IntEnumType((int)myTypes.tipo2, myTypes.tipo2.ToString()),
};
return _statusList;
}
}
private int myVar = 1;
public int MyProperty
{
get
{
return myVar;
}
set
{
if (myVar != value)
{
myVar = value;
OnPropertyChanged(() => MyProperty);
}
}
}
}
public class TabItemStyleSelector : StyleSelector
{
public Style MainTabItem { get; set; }
public Style ChildrenTabItem { get; set; }
public Style SpecificationTabItem { get; set; }
public override Style SelectStyle(object item, DependencyObject container)
{
//if (item is IHome)
// return MainTabItem;
//else if (item is SpecificationItemViewModel)
// return SpecificationTabItem;
//else
return ChildrenTabItem;
}
}
The code is a little hard to completely follow, but I'm guessing that the issue is that there is only one instance of your ClassViewModel and it is where the selection for the combo box is stored {Binding Path=MyProperty, so whatever is stored in MyProperty will be reflected in all instances of the combo box regardless of where they live.
Well this is a bit late, but as I'm facing the same issue, I want to share my analysis.
When you change your tabs, you change the DataContext of the current Tab to your other ViewModel and hence also the ItemsSource of your ComboBox.
In case your previously selected Item (SelectedItem) is not contained within the new ItemsSource, the ComboBox fires a SelectionChanged-Event and therefore sets the SelectedIndex to -1.
Altough this default behaviour of the ComboBox might make sense, it's very annoying in many cases.
We've derived an own class from ComboBox, handling that. But it's not very satisfying as you loose some default behaviour you most probably need.
The problem is in your loaded event handlers.
When you switch tabs your unloading one tab and loading a new one.
I suspect your changing MyComboBox.SelectedIndex in _spl2Status_Loaded.

Categories