I am playing with WPF TreeView control and facing strange problem.
When I add TreeView items programmatically all working correct, but in case I have added items through binding - I have Items with gap in header:
furthermore, this gap is only one "sensitive" part of the TreeItem's line. I cant select item by clicking on the text, it can be selected only if I click on the gap area.
Here is my code:
namespace WPFTreeViewExperience
{
/// <summary>
/// Data item interface
/// </summary>
public interface IMyTree
{
string Title { get; set; }
List<IMyTree> Items { get; set; }
}
/// <summary>
/// Hierarchical data model
/// </summary>
public class MyTreeLevel : IMyTree
{
public string Title { get; set; }
public List<IMyTree> Items { get; set; }
public MyTreeLevel(string Title, params MyTreeLevel[] Items)
{
this.Title = Title;
this.Items = new List<IMyTree>();
foreach (MyTreeLevel item in Items)
{
this.Items.Add(item);
}
}
}
/// <summary>
/// Viewmodel
/// </summary>
public class MyTreeViewModel : INotifyPropertyChanged
{
private void NotifyChanges(string PropName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
}
private List<MyTreeLevel> _Tree;
public List<MyTreeLevel> MyTree
{
get
{
return _Tree;
}
set
{
_Tree = value;
NotifyChanges("MyTree");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MyTreeViewModel()
{
List<MyTreeLevel> simpleTree = new List<MyTreeLevel>();
simpleTree.Add(new MyTreeLevel("1-0", new MyTreeLevel("1-1"),
new MyTreeLevel("1-2",
new MyTreeLevel("1-2-1"),
new MyTreeLevel("1-2-2")
),
new MyTreeLevel("1-3")));
simpleTree.Add(new MyTreeLevel("2-0", new MyTreeLevel("2-1"),
new MyTreeLevel("2-2",
new MyTreeLevel("2-2-1"),
new MyTreeLevel("2-2-2"),
new MyTreeLevel("2-3"))));
MyTree = simpleTree;
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TreeExample.DataContext = new MyTreeViewModel();
}
}
}
and my XAML code:
<Window x:Class="WPFTreeViewExperience.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFTreeViewExperience"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView Name="TreeExample" ItemsSource="{Binding MyTree}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}" DataType="{x:Type local:MyTreeLevel}">
<TreeViewItem Header="{Binding Title}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
What is wrong with my binding?
I think that ItemTemplate should be something like this:
<HierarchicalDataTemplate ItemsSource="{Binding Items}" DataType="{x:Type local:MyTreeLevel}">
<Grid>
<TextBlock Margin="0" Text="{Binding Path=Title}" />
</Grid>
</HierarchicalDataTemplate>
Related
I'm a newbie in wpf and i know that this question has been asked other times, and i tried to implement some solutions that i found. But it's not working. I'm doing something wrong but i can't see what it is.
I've created a new simple application to test this problem.
namespace WpfApp3
{
public class MyElement
{
public string Text { get; set; }
public MyElement(string t)
{
Text = t;
}
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler(parameter);
}
private Action<object> _handler;
public MyCommand(Action<object> handler) { _handler = handler; }
}
public class MyItemsControlViewModel
{
ObservableCollection<MyElement> _items;
public ObservableCollection<MyElement> MyElementItems { get { return _items; } set { _items = value; RaisePropertyChanged("MyElementItems"); } }
ObservableCollection<MyElement> _temporayList;
private ICommand _itemClicked;
public ICommand ItemClicked { get { return _itemClicked; } }
public MyItemsControlViewModel()
{
_items = new ObservableCollection<MyElement>();
_temporayList = new ObservableCollection<MyElement>();
_itemClicked = new MyCommand(OnItemSelected);
AddItem("Element 1");
AddItem("Element 2");
AddItem("Element 3");
UpdateList();
}
public void UpdateList()
{
MyElementItems = _temporayList;
}
public void AddItem(string t)
{
MyElement item = new MyElement(t);
_temporayList.Add(item);
}
public void OnItemSelected(object param)
{
Debug.WriteLine("Executed!");
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML
<UserControl x:Class="WpfApp3.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="570"
x:Name="myCustomControl">
<Grid >
<Button x:Name="btnOutsideItemsControl" Width="100" Height="100 " VerticalAlignment="Top" Command="{Binding ItemClicked}" />
<ItemsControl
x:Name="listItems"
ScrollViewer.PanningMode="None"
IsEnabled="False"
Background = "Transparent"
HorizontalAlignment="Center"
ItemsSource="{Binding MyElementItems}" Margin="0,152,0,0" Width="549">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" Margin="50,0,0,0"
Background="Transparent" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Text}"
Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The component is used in MainWindow.xaml.
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyItemsControlViewModel _myViewModel;
public MyItemsControlViewModel MyViewModel { get { return _myViewModel; } }
public MainWindow()
{
_myViewModel = new MyItemsControlViewModel();
InitializeComponent();
myCustomControl.DataContext = MyViewModel;
}
}
}
XAML
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:MyUserControl x:Name="myCustomControl"/>
</Grid>
</Window>
When i run the application i can see correctly the list of 3 items with the correct text.
But if i click on one of the button of the list i can't see the output of Debug.WriteLine("Executed!");
But if i click on the button btnOutsideItemsControl that is outside the ItemsControl, it works. I can see the output of Debug.WriteLine("Executed!");
So i think that also the definition of the command is correct.
To bind correctly the Command property of Button inside the ItemsControl i try this
<Button Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}">
And also this
<Button Command="{Binding DataContext.ItemClicked, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}">
But it not works.
Please help!
You're gonna kick yourself once I tell you.
Your problem is that you set IsEnabled="False" on your ItemsControl. Remove it and all will be well with the universe.
In the process of trying to learn WFP, I have taken the task of porting some old Winform apps into WPF and trying to stick to a MVVM model.
In the Winform app, I have a set of checkboxes that change the state of a BitArray, which in turns gets sent over TCP. Simple Stuff.
How would I do this in WPF and databinding? How can i bind a specific checkbox to a specific bit in the BitArray? All the examples i have found of this databind to single boolean property in VM.
EDIT:
I found the solution here by using an ObservableCollection>:
How to bind an ObservableCollection<bool> to a Listbox of Checkboxes in WPF
What i don't understand is what is the purpose of:
public static implicit operator Wrapper<T>(T value)
{
return new Wrapper<T> { value = value };
}
public static implicit operator T(Wrapper<T> wrapper)
{
return wrapper.value;
}
Inside the wrapper class, can someone explain what this does and why its needed?
The benefit of using MVVM is that you can taylor view models to suite your needs.
Create an Item class to track the status of each bit in the array.
Create a MVVM view model with an observable collection of your Item object
Databind your view model in code behind
Decorate your xaml with binding info
That's it! Enjoy!
See an Screenshot
Download a Full Example on GitHub
C#
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
namespace DataBindingBitArray
{
/// <summary>
/// 1. Create an Item class to track the status of each bit in the array.
/// </summary>
/// <seealso cref="System.ComponentModel.INotifyPropertyChanged" />
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int BitArrayIndex { get; set; }
public BitArray ParentBitArray { get; set; }
private bool isChecked;
public Item(int bitArrayIndex, bool isChecked, BitArray parentBitArray)
{
this.BitArrayIndex = bitArrayIndex;
this.isChecked = isChecked;
this.ParentBitArray = parentBitArray;
}
public bool IsChecked
{
get => isChecked;
set
{
if (ParentBitArray != null)
{
ParentBitArray[BitArrayIndex] = isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// 2. Create a MVVM view model with an observable collection of your Item object
/// </summary>
/// <seealso cref="System.ComponentModel.INotifyPropertyChanged" />
public class BitArrayViewModel : INotifyPropertyChanged
{
private readonly BitArray bitArray;
private ObservableCollection<Item> items;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Item> Items
{
get => items;
set
{
items = value;
OnPropertyChanged(nameof(Items));
}
}
public BitArrayViewModel(BitArray bitArray)
{
this.bitArray = bitArray;
var query = this
.bitArray
.Cast<bool>()
.Select((s, i) => new Item(i, s, this.bitArray));
this.Items = new ObservableCollection<Item>(query);
}
public int CountOnBits()
{
return this.bitArray.Cast<bool>().Count(s => s);
}
public int CountOffBits()
{
return this.bitArray.Cast<bool>().Count(s => !s);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// 3 . Databind your view model in code behind
/// </summary>
/// <seealso cref="System.Windows.Window" />
/// <seealso cref="System.Windows.Markup.IComponentConnector" />
public partial class MainWindow : Window
{
public BitArrayViewModel ViewModel;
public MainWindow()
{
InitializeComponent();
this.DataContext = ViewModel = new BitArrayViewModel(new BitArray(100));
MessageBox.Show($"You have {ViewModel.CountOnBits()} on bits and {ViewModel.CountOffBits()} off bits");
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show($"You have {ViewModel.CountOnBits()} on bits and {ViewModel.CountOffBits()} off bits");
}
}
}
XAML
<Window x:Class="DataBindingBitArray.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:DataBindingBitArray"
mc:Ignorable="d"
Height="360" Width="250">
<StackPanel Height="300" Margin="10">
<Label Height="40" Margin="5" FontSize="18">Binding to Bit Array</Label>
<ScrollViewer Height="200">
<ItemsControl Margin="5" x:Name="ItemsControl1" ItemsSource="{Binding Path=Items}" HorizontalAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Content ="{Binding Path=BitArrayIndex }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Button Height="40" Margin="5" Click="ButtonBase_OnClick" Content="Show BitArray Status"></Button>
</StackPanel>
</Window>
i would like to ask how to Populate listview using MVVM pattern i'm beginner in mvvm pattern, i learn more from doing things than reading. i've done this before using wpf but i use code behind.
I use Mvvm Light. What i want is browse the Location of the folder and then populate the listview with the files inside it
so far i already have a Browse folder
i have this code
public class OpenFileDialogVM : ViewModelBase
{
public static RelayCommand OpenCommand { get; set; }
private string _selectedPath;
public string SelectedPath
{
get { return _selectedPath; }
set
{
_selectedPath = value;
RaisePropertyChanged("SelectedPath");
}
}
private string _defaultPath;
public OpenFileDialogVM()
{
RegisterCommands();
}
public OpenFileDialogVM(string defaultPath)
{
_defaultPath = defaultPath;
RegisterCommands();
}
private void RegisterCommands()
{
OpenCommand = new RelayCommand(ExecuteOpenFileDialog);
}
private void ExecuteOpenFileDialog()
{
var dialog = new FolderBrowserDialog();
dialog.ShowDialog();
SelectedPath = dialog.SelectedPath;
}
}
and i have this code for user control
<UserControl x:Class="MvvmLight1.FolderDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:MvvmLight1"
xmlns:local="clr-namespace:MvvmLight1"
mc:Ignorable="d" d:DesignWidth="300" Height="186.916" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="90*"/>
<RowDefinition Height="97*"/>
</Grid.RowDefinitions>
<Grid>
<TextBox Text="{Binding SelectedPath}" />
</Grid>
<Grid Grid.Row="1" >
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>
</Grid>
</Grid>
</UserControl>
so far the browse is working. my question is how can i call this code. after the selection of folder so that i can populate my listview?
private void Call(string selectedpath)
{
try
{
var allFiles = Directory.GetFiles(selectedpath, "*", SearchOption.AllDirectories);
foreach (var item in allFiles)
{
System.Console.WriteLine(item);
//code for populating listview
}
}
catch (System.Exception ex)
{
System.Console.WriteLine(ex.StackTrace);
throw ex;
}
}
Thank you for time.
Make Files a public observable collection.
Raise the PropertyChanged event.
Set the window's datacontext to your viewmodel.
Bind to the viewmodel's property in xaml.
CS
using System;
using System.ComponentModel;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Collections.ObjectModel;
namespace StackOverflow_PopulateListView
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new OpenFileDialogVM();
}
}
public class OpenFileDialogVM : ViewModelBase
{
public static RelayCommand OpenCommand { get; set; }
private string _selectedPath = "Enter a Path";
public string SelectedPath
{
get { return _selectedPath; }
set
{
_selectedPath = value;
OnPropertyChanged("SelectedPath");
}
}
private ObservableCollection<string> _files = new ObservableCollection<string>() { "Tits", "balls", "ass", "tits" };
public ObservableCollection<string> Files
{
get { return _files; }
set
{
_files = value;
OnPropertyChanged("Files");
}
}
private ICommand _selectFileCommand;
public ICommand SelectFileCommand
{
get
{
return _selectFileCommand ?? (_selectFileCommand = new RelayCommand(() => Call(SelectedPath)));
}
protected set
{
_selectFileCommand = value;
}
}
public void Call(string selectedpath)
{
try
{
Files = new ObservableCollection<string>(Directory.GetFiles(selectedpath, "*", SearchOption.AllDirectories));
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
throw ex;
}
}
}
public class RelayCommand : ICommand
{
public Action Act { get; set; }
/// <summary> Occurs when the target of the Command should reevaluate whether or not the Command can be executed. </summary>
public event EventHandler CanExecuteChanged;
public RelayCommand(Action act)
{
Act = act;
}
/// <summary> Returns a bool indicating if the Command can be exectued with the given parameter </summary>
public bool CanExecute(object obj)
{
return true;
}
/// <summary> Send a ICommand.CanExecuteChanged </summary>
public void ChangeCanExecute()
{
object sender = this;
EventArgs eventArgs = null;
CanExecuteChanged(sender, eventArgs);
}
/// <summary> Invokes the execute Action </summary>
public void Execute(object obj)
{
Act();
}
}
/// <summary>
/// Extremely generic ViewModelBase; for copy-pasting into almost any MVVM project
/// </summary>
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Fires PropertyChangedEventHandler, for bindables
/// </summary>
protected virtual void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
XAML
<Window x:Class="StackOverflow_PopulateListView.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:StackOverflow_PopulateListView"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Files}" Grid.Row="0">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox Grid.Row="1"
Text="{Binding SelectedPath}"
/>
<Button Grid.Row="2"
Content="Call"
Command="{Binding SelectFileCommand}"
/>
</Grid>
</Window>
Your view model should have an ObservableCollection of file names
public ObservableCollection<string> FileNames { get; }
= new ObservableCollection<string>();
which is populated when a directory is selected:
var files = Directory.EnumerateFiles(selectedpath, "*", SearchOption.AllDirectories);
FileNames.Clear();
foreach (var file in files)
{
FileNames.Add(file);
}
You would then bind the ItemsSource property of a ListBox to that collection:
<ListBox ItemsSource="{Binding FileNames}"/>
I'm working on adding some properties to an extension class of TreeView. I need the extra fields for context when one of the items in the tree is clicked. I can't seem to get the tree view to show any of the data I'm giving it.
In my MainView.cs I'm simply setting the items source as such:
TreeMenu.ItemsSource = (an ObservableCollection of ParentItems)
XAML:
<Grid x:Name="TreeGrid" Width="350" HorizontalAlignment="Left">
<TreeView Name="TreeMenu" Background="Red" Foreground="Black">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type model:ParentItem}" ItemsSource="{Binding ChildItems}">
<TextBlock Text="{Binding Text}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
Object model:
public class ParentItem : TreeViewItem, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
public ParentItem()
{
_text = "";
_url = "";
_childItems = new ObservableCollection<ChildItem>();
}
private string _text;
public string Text
{
get
{
return _text;
}
set
{
_text = value;
NotifyPropertyChanged("Text");
}
}
private string _url;
public string URL
{
get
{
return _url;
}
set
{
_url = value;
NotifyPropertyChanged("URL");
}
}
private ObservableCollection<ChildItem> _childItems;
public ObservableCollection<ChildItem> ChildItems
{
get
{
return _childItems;
}
set
{
_childItems = value;
NotifyPropertyChanged("ChildItems");
}
}
}
Note that ChildItem is almost identical to ParentItem, minus the collection object. Originally I tried extending TreeNode in my object classes, but that had the same issue.
Does anyone know why my TreeView won't appear? Am I missing something while extending TreeView?
No point in extending TreeViewItem.
We don't see how you assign your collection so they might something done wrongly.
This does work:
Code
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ItemCollection
{
new Item
{
Text = "A",
Items = new ItemCollection
{
new Item
{
Text = "B",
Items = new ItemCollection
{
new Item
{
Text = "C"
}
}
}
}
}
};
}
}
public class Item
{
public string Text { get; set; }
public ItemCollection Items { get; set; }
}
public class ItemCollection : ObservableCollection<Item>
{
}
}
XAML
<Window x:Class="WpfApplication4.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:WpfApplication4"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="local:Item" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Text}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Result
I have a combobox in a tab item in MVVM. This tab can be created multiple times in my application (same view, same view model but different instance), so I can switch from one tab to another (but they are tab of the same type).
It works perfectly with every WPF control, but with combobox I have a strange behaviour:
the focus tab, when it loses focus, gets the selected item of the combox box of the tab that the application is focusing on.
If I switch from 2 tabs that are not of the same type everything works correctly, any idea about that? Thanks.
XAML:
<CollectionViewSource x:Key="StatusView" Source="{Binding Path=StatusList}"/>
<ComboBox Name="_spl2Status" Grid.Column="3" Grid.Row="0"
ItemsSource="{Binding Source={StaticResource StatusView}}"
SelectedValue="{Binding Path=CurrentSPL2.ID_SPL2_STATUS, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="FL_TYPE"
DisplayMemberPath="ID_TYPE">
</ComboBox>
VM:
public List<NullableByteEnumType> StatusList
{
get
{
return (SPC_SPL2.SPL2StatusCollection.Skip(1)).ToList();
}
}
private SPC_SPL2 _currentSPL2 = null;
public SPC_SPL2 CurrentSPL2
{
get
{
if (_currentSPL2== null)
Controller.Execute(delegate(IResult result)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("FL_ACTIVE", true);
parameters.Add("ID_SPL2", _itemcode);
Model.Invalidate(typeof(SPC_SPL2), Filter.GENERIC<SPC_SPL2>(parameters, "ID_SPL2"));
Model.Include<SPC_SPL2>();
if (Model.Appendload(result) == false)
return false;
Debug.Assert(Context.SPC_SPL2.Count == 1);
_currentSPL2= Context.SPC_SPL2.FirstOrDefault();
return result.Successful;
});
return _currentSPL2;
}
set
{
_currentSPL2= value;
OnPropertyChanged(() => CurrentSPL2);
}
}
my tabs are handled in this way:
<Grid>
<Border Grid.Row="0">
<ContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource MasterWorkspacesTemplate}"
/>
</Border>
</Grid>
where
<DataTemplate x:Key="MasterWorkspacesTemplate">
<TabControl IsSynchronizedWithCurrentItem="True"
BorderThickness="0"
ItemsSource="{Binding}"
SelectedItem="{Binding}"
ItemContainerStyleSelector="{StaticResource TabItemTemplate}"
/>
</DataTemplate>
and workspaces (my viewmodels list) (T is a class who inherit from viewModelBase)
public T CurrentWorkspace
{
get { return WorkspacesView.CurrentItem as T; }
}
private ObservableCollection<T> _workspaces;
public ObservableCollection<T> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<T>();
_workspaces.CollectionChanged += _OnWorkspacesChanged;
}
return _workspaces;
}
}
protected ICollectionView WorkspacesView
{
get
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(Workspaces);
Debug.Assert(collectionView != null);
return collectionView;
}
}
I have recreated your problem. But I couldn't find any issue. Please look at the code below and you might get the solustion. Here is my solution.
MyTab view model
public class MyTab : ViewModelBase
{
#region Declarations
private ObservableCollection<string> statusList;
private string selectedStatus;
#endregion
#region Properties
/// <summary>
/// Gets or sets the header.
/// </summary>
/// <value>The header.</value>
public string Header { get; set; }
/// <summary>
/// Gets or sets the content.
/// </summary>
/// <value>The content.</value>
public string Content { get; set; }
/// <summary>
/// Gets or sets the status list.
/// </summary>
/// <value>The status list.</value>
public ObservableCollection<string> StatusList
{
get
{
return statusList;
}
set
{
statusList = value;
NotifyPropertyChanged("StatusList");
}
}
/// <summary>
/// Gets or sets the selected status.
/// </summary>
/// <value>The selected status.</value>
public string SelectedStatus
{
get
{
return selectedStatus;
}
set
{
selectedStatus = value;
NotifyPropertyChanged("SelectedStatus");
}
}
#endregion
}
MainViewModel view model
public class MainViewModel : ViewModelBase
{
#region Declarations
private ObservableCollection<MyTab> tabs;
private MyTab selectedTab;
#endregion
#region Properties
/// <summary>
/// Gets or sets the tabs.
/// </summary>
/// <value>The tabs.</value>
public ObservableCollection<MyTab> Tabs
{
get
{
return tabs;
}
set
{
tabs = value;
NotifyPropertyChanged("Tabs");
}
}
/// <summary>
/// Gets or sets the selected tab.
/// </summary>
/// <value>The selected tab.</value>
public MyTab SelectedTab
{
get
{
return selectedTab;
}
set
{
selectedTab = value;
NotifyPropertyChanged("SelectedTab");
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MainViewModel"/> class.
/// </summary>
public MainViewModel()
{
this.Tabs = new ObservableCollection<MyTab>();
MyTab tab1 = new MyTab();
tab1.Header = "tab1";
tab1.Content = "Tab 1 content";
ObservableCollection<string> tab1StatusList = new ObservableCollection<string>();
tab1StatusList.Add("tab1 item1");
tab1StatusList.Add("tab1 item2");
tab1StatusList.Add("tab1 item3");
tab1.StatusList = tab1StatusList;
tab1.SelectedStatus = tab1StatusList.First();
this.Tabs.Add(tab1);
MyTab tab2 = new MyTab();
tab2.Header = "tab2";
tab2.Content = "Tab 2 content";
ObservableCollection<string> tab2StatusList = new ObservableCollection<string>();
tab2StatusList.Add("tab2 item1");
tab2StatusList.Add("tab2 item2");
tab2StatusList.Add("tab2 item3");
tab2.StatusList = tab2StatusList;
tab2.SelectedStatus = tab2StatusList.First();
this.Tabs.Add(tab2);
this.SelectedTab = tab1;
}
#endregion
}
And finally this is my XAML
<Window x:Class="ComboboxSelectedItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:ComboboxSelectedItem.ViewModels"
Title="MainWindow" Height="350" Width="525">
<Grid Name="mainGrid">
<Grid.DataContext>
<viewModel:MainViewModel />
</Grid.DataContext>
<TabControl
ItemsSource="{Binding Tabs, Mode=TwoWay}"
SelectedItem="{Binding SelectedTab}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" Margin="0 0 20 0"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<!--Content section-->
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock
Text="{Binding Content}" />
<ComboBox
ItemsSource="{Binding StatusList}"
SelectedItem="{Binding SelectedStatus}" />
</StackPanel>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Are you absolutely sure that you are creating a new instance of the viewmodel. If not, then the comboboxes are sharing the same collectionviewsource which means that a change in one combobox will be reflected in all comboboxes. I had this same problem myself.
Try declaring the collection view source in code:
CollectionViewSource StatusListViewSource = new CollectionViewSource();
StatusListViewSource.Source = SPL2StatusCollection;
then in xaml change binding to the collectionviewsource:
ItemsSource="{Binding StatusListViewSource.View}"
I converted from vb so it might need some edits.
Does that help?