I have a class like this :
public class Project
{
List<Object> list1;
List<Object> list2;
}
I want to show this in a treeview control like as follows :
Checkbox + "Listing1"
--> Checkbox + Object 1 of list1
--> Checkbox + Object 2 of list1
Checkbox + "Listing2"
--> Checkbox + Object 1 of list2
-->Checkbox + Object 2 of list2
My biggest problem is making the difference between the 2 lists + some extra : if list2 does not contain any objects, the "Listing2" header may not be shown.
Does anybody have any good idea how I can do this ?
You can create one class TreeViewItemWithCheckbox by extending TreeViewItem class like below :
public class TreeViewItemWithCheckbox : TreeViewItem
{
#region Variable Declaration
CheckBox chkBox = new CheckBox();
StackPanel stpContent = new StackPanel();
#endregion
#region Properties
public string HeaderText
{
get { return chkBox.Content.ToString(); }
set { chkBox.Content = value; }
}
public bool IsChecked
{
get { return chkBox.IsChecked.Value; }
set { chkBox.IsChecked = value; }
}
#endregion
#region Constructor
public TreeViewItemWithCheckbox()
{
stpContent.Orientation = Orientation.Horizontal;
chkBox = new CheckBox();
chkBox.VerticalAlignment = VerticalAlignment.Center;
chkBox.Click += new RoutedEventHandler(SetCheckboxes);
chkBox.Margin = new Thickness(0, 0, 0, 0);
stpContent.Children.Add(chkBox);
Header = stpContent;
}
#endregion
#region Event Handlers
private void SetCheckboxes(object sender, RoutedEventArgs e)
{
TreeViewItemWithCheckbox selectedItem = ((TreeViewItemWithCheckbox)((StackPanel)((CheckBox)sender).Parent).Parent);
if (selectedItem != null)
{
/* Set checkboxes for all child items */
if (selectedItem.Items.Count > 0)
{
SetChildItemCheckboxes(selectedItem, selectedItem.IsChecked);
}
/* Check if all childs checked then check/uncheck parent accoringly */
if (selectedItem.Parent.GetType() == typeof(TreeViewItemWithCheckbox))
{
TreeViewItemWithCheckbox parentItem = (TreeViewItemWithCheckbox)selectedItem.Parent;
bool bIsAllChecked = true;
foreach (TreeViewItemWithCheckbox item in parentItem.Items)
{
if (!item.IsChecked)
{
bIsAllChecked = false;
break;
}
}
parentItem.IsChecked = bIsAllChecked;
}
}
}
private void SetChildItemCheckboxes(TreeViewItemWithCheckbox item, bool IsChecked)
{
if (item.Items.Count > 0)
{
foreach (TreeViewItemWithCheckbox childItem in item.Items)
{
SetChildItemCheckboxes(childItem, IsChecked);
item.IsChecked = IsChecked;
}
}
else
item.IsChecked = IsChecked;
}
#endregion
}
Then you need to add treeview nodes from 2 list like below :
trvTest.Items.Clear();
//Add default root element
TreeViewItemWithCheckbox rootNode = new TreeViewItemWithCheckbox();
rootNode.HeaderText = "Root";
rootNode.IsExpanded = true;
trvTest.Items.Add(rootNode);
if (_project.list1.Count > 0)
{
TreeViewItemWithCheckbox nodeHead1 = new TreeViewItemWithCheckbox();
nodeHead1.HeaderText = "Listing 1";
rootNode.Items.Add(nodeHead1);
TreeViewItemWithCheckbox node1;
for (int i = 0; i < _project.list1.Count; i++)
{
node1 = new TreeViewItemWithCheckbox();
node1.HeaderText = _project.list1[i].Name;
nodeHead1.Items.Add(node1);
}
}
if (_project.list2.Count > 0)
{
TreeViewItemWithCheckbox nodeHead2 = new TreeViewItemWithCheckbox();
nodeHead2.HeaderText = "Listing 2";
rootNode.Items.Add(nodeHead2);
TreeViewItemWithCheckbox node2 = new TreeViewItemWithCheckbox();
for (int i = 0; i < _project.list2.Count; i++)
{
node2 = new TreeViewItemWithCheckbox();
node2.HeaderText = _project.list2[i].Name;
nodeHead2.Items.Add(node2);
}
}
This can be solved without TreeView - Just 2 CheckBoxes and 2 ItemsControl with specific ItemTemplate.
I prefer this way of solving this problem:
<StackPanel>
<StackPanel.Resources>
<DataTemplate x:Key="MyTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding SomeProperty}" IsChecked="{Binding SomeBooleanProperty}" />
</StackPanel>
</DataTemplate>
</StackPanel.Resources>
<CheckBox Content="List number 1" />
<ItemsControl ItemsSource="{Binding list1}" ItemTemplate="{StaticResource MyTemplate}" />
<CheckBox Content="List number 2" />
<ItemsControl ItemsSource="{Binding list2}" ItemTemplate="{StaticResource MyTemplate}" />
</StackPanel>
As for your extra: you can bind Visibility to the property in your Project class, which will be looks like:
public class Project
{
public List<Object> list1;
public List<Object> list2;
public Visibility Visuality
{
get
{
return this.list2.Any() ? Visibility.Visible : Visibility.Colapsed;
}
}
}
and than in the code:
<ItemsControl Visibility="{Binding Visuality}" ... />
<CheckBox Visibility="{Binding Visuality}" ... />
...if I understand you right...
If your first "Listing" items are static, you can do something like this
<TreeView x:Name="tv">
<TreeViewItem Header="Listing 1" ItemsSource="{Binding list1}"/>
<TreeViewItem Header="Listing 2" ItemsSource="{Binding list2}"/>
</TreeView>
To hide elements without children you need a bit of code.
var view = CollectionViewSource.GetDefaultView(_project.list1);
view.Filter = myFilter;
view.Refresh();
tv.DataContext = _project;
For both lists of course.
Related
I am trying to create a wpf combobox containing a list of World of warcraft items. I am using a item template for the combo box so that the items icon and item name are shown in the combobox drop down list. It works as is and I can start typing a item name and the list of items in the popup are automatically filtered.
The problem is that when I click an item I want the item name to be shown in the comboboxes text area. Instead what I get is a very brief ItemDisplayModel.ToString() then a empty string.
The ItemDisplayModel.ToString() being the ItemDisplayModel type that I selected from the list. Which makes sense cause the combobox control ItemsSource property contains an array of ItemDisplayModel types.
But in my AuctionHouseFilterItemNameModel.Value property I am detecting weather the value has changed and if so rebuild the list of items to be displayed in the combobox. So because the Text in the combobox was changed to ItemDisplayModel.ToString() when I click an item in the popup list it again rebuilds the list of items only this time there is no match because ItemDisplayModel.ToString() does not match any known item names.
All I am trying to do is when I click an item in the popup list I want the ItemDisplayModel.Name to be mapped to the AuctionHouseFilterItemNameModel.Value property and have the combobox text area to display AuctionHouseFilterItemNameModel.Value as well.
Does that make sense? I've tried numerous different ways and I am at a loss. Looking around the web did'nt give me the answer either. I'm sure it's something simple to fix but the solution eludes me.
Here is my XAML for the control
<UserControl
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:AuctionHouseSearchFilters="clr-namespace:Codefarts.WowTracks.DataMinerWPF.Models.AuctionHouseSearchFilters"
xmlns:Models="clr-namespace:Codefarts.WowTracks.DataMinerAppCore.Models;assembly=Codefarts.WowTracks.DataMinerAppCore"
xmlns:DataMinerAppCore="clr-namespace:Codefarts.WowTracks.DataMinerAppCore;assembly=Codefarts.WowTracks.DataMinerAppCore"
xmlns:dataMinerWpf="clr-namespace:Codefarts.WowTracks.DataMinerWPF"
x:Name="userControl"
x:Class="Codefarts.WowTracks.DataMinerWPF.Controls.AuctionHouseSearchFilters.AuctionHouseItemNameControl"
mc:Ignorable="d"
d:DesignHeight="51" d:DesignWidth="283">
<UserControl.Resources>
<dataMinerWpf:UriToBitmapImageConverter x:Key="UriToImageConverter" />
<BitmapImage x:Key='defaultImage' UriSource='/Resources\118.png' />
<DataTemplate x:Key="ItemTemplate" DataType="Models:ItemDisplayModel" >
<StackPanel Orientation="Horizontal" Margin="0 5 0 5">
<Image Width="50" Height="50" Stretch="Fill" Source="{Binding IconUrl, Converter={StaticResource UriToImageConverter}, TargetNullValue={StaticResource defaultImage}}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Content="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<UserControl.DataContext>
<AuctionHouseSearchFilters:AuctionHouseFilterItemNameModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<ComboBox x:Name="ItemList" MaxDropDownHeight="592" Grid.Row="0" ItemsSource="{Binding ItemDisplayModels}" ItemTemplate="{StaticResource ItemTemplate}" IsEditable="True" IsTextSearchEnabled="True" IsTextSearchCaseSensitive="False" Text="{Binding Value}" >
</ComboBox>
</Grid>
</UserControl>
Here is my code behind
public partial class AuctionHouseItemNameControl : UserControl, IAuctionHouseFilterControl
{
private Application app;
public AuctionHouseItemNameControl()
{
InitializeComponent();
}
public void SetModel(IAuctionHouseSearchFilter model)
{
this.DataContext = model;
}
public void SetApplication(Application app)
{
this.app = app;
}
private void ItemList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//if (model.Handled)
//{
// return;
//}
// model.Handled = true;
if (e.AddedItems != null && e.AddedItems.Count > 0)
{
var model = this.DataContext as Models.AuctionHouseSearchFilters.AuctionHouseFilterItemNameModel;
var item = e.AddedItems[0] as ItemDisplayModel;
// this.ItemList.SelectedValue = item.Name;
model.Value = item.Name;
// this.ItemList.SelectedItem = item.Name;
}
// model.Handled = false;
}
}
and here is my data models
[Export(typeof(IAuctionHouseSearchFilter))]
public class AuctionHouseFilterItemNameModel : IAuctionHouseSearchFilter, INotifyPropertyChanged
{
private string value;
private bool connected;
private Application app;
private ItemDisplayModel[] displayItems;
private CancellationTokenSource backgroundCancel;
private bool buildingDisplayItems;
private readonly object lockObject = new object();
private ItemDisplayModel displayValue;
// private bool Handled { get; set; }
public ItemDisplayModel DisplayValue
{
get
{
return this.displayValue;
}
set
{
if (value == null)
{
return;
}
this.Value = value.Name;
this.displayValue = value;
this.OnPropertyChanged();
}
}
public string Value
{
get
{
return this.value;
}
set
{
//if (this.Handled)
//{
// return;
//}
if (value == this.value)
{
return;
}
this.value = value;
// this.Handled = true;
this.OnPropertyChanged();
var cancellationTokenSource = this.backgroundCancel;
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
}
this.BuildDisplayItems();
// this.Handled = false;
}
}
public IEnumerable<AuctionDataModel> Filter(MainFilterModel model, IEnumerable<AuctionDataModel> items)
{
if (!this.connected)
{
return items;
}
// get item id from it's name
var list = this.app.ItemNames[model.SelectedRegion];
var id = list.FirstOrDefault(x => x.Name.ToLowerInvariant().Contains(this.value.Trim().ToLowerInvariant()));
return items.Where(x => id != null && x.ItemId == id.Id);
}
public IEnumerable<ItemDisplayModel> ItemDisplayModels
{
get
{
return this.displayItems;
}
}
public async void BuildDisplayItems()
{
if (this.buildingDisplayItems)
{
return;
}
if (!this.connected)
{
this.displayItems = null;
}
// return this.GetDisplayItems();
this.buildingDisplayItems = true;
this.backgroundCancel = new CancellationTokenSource();
this.OnPropertyChanged("ItemDisplayModels");
await Task.Factory.StartNew(
() =>
{
var originalItems = this.displayItems;
this.displayItems = new[] { new ItemDisplayModel() { Name = "Rebuilding list..." } };
var correctedSearchValue = this.value.Trim().ToLowerInvariant();
//lock (this.lockObject)
//{
this.displayItems = (string.IsNullOrWhiteSpace(this.value) ?
this.app.DisplayItemModels :
this.app.DisplayItemModels.Where(x => x.Name.ToLowerInvariant().Contains(correctedSearchValue))).Take(100).AsParallel().ToArray();
this.buildingDisplayItems = false;
this.OnPropertyChanged("ItemDisplayModels");
},
this.backgroundCancel.Token);
}
public string Name
{
get
{
return "Item Name";
}
}
public Type Control
{
get
{
return typeof(AuctionHouseItemNameControl);
}
}
public virtual IAuctionHouseSearchFilter Clone()
{
return new AuctionHouseFilterItemNameModel()
{
Value = this.Value
};
}
public void Connect(Application app)
{
if (this.connected)
{
return;
}
this.app = app;
this.connected = true;
}
public void Disconnect()
{
if (!this.connected)
{
return;
}
this.app = null;
this.connected = false;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ItemDisplayModel : INotifyPropertyChanged
{
private string iconUrl;
private string region;
private string name;
private int itemId;
public int ItemId
{
get
{
return this.itemId;
}
set
{
if (value == this.itemId)
{
return;
}
this.itemId = value;
this.OnPropertyChanged();
}
}
public string Name
{
get
{
return this.name;
}
set
{
if (value == this.name)
{
return;
}
this.name = value;
this.OnPropertyChanged();
}
}
public string Region
{
get
{
return this.region;
}
set
{
if (value == this.region)
{
return;
}
this.region = value;
this.OnPropertyChanged();
}
}
public string IconUrl
{
get
{
return this.iconUrl;
}
set
{
if (value == this.iconUrl)
{
return;
}
this.iconUrl = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The problem is that when I click an item I want the item name to be shown in the comboboxes text area. Instead what I get is a very brief ItemDisplayModel.ToString() then a empty string.
To get the Name property displayed in the editable text area of the ComboBox use TextSearch.TextPath so your ComboBox definition would look like:
<ComboBox x:Name="ItemList"
Grid.Row="0"
IsEditable="True"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding ItemDisplayModels}"
MaxDropDownHeight="592"
TextSearch.TextPath="Name" />
Now when an item is selected from the dropdown instead of seeing ItemDisplayModel.ToString(), you will see the Name property.
I have bound the DataTable to the DataGrid control. How can I set the selected item programmatically ?
Example
In my view model I have a property of type DataTable to bind the DataGrid
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get
{
return sizeQuantityTable;
}
set
{
sizeQuantityTable = value;
NotifyPropertyChanged("SizeQuantityTable");
}
}
My XAML
<DataGrid
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,120" />
The constructor of the view model (assigning dummy values)
this.SizeQuantityTable = new DataTable();
DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
this.SizeQuantityTable.Columns.Add(sizeQuantityColumn);
DataColumn sColumn = new DataColumn();
sColumn.ColumnName = "S";
this.SizeQuantityTable.Columns.Add(sColumn);
DataColumn mColumn = new DataColumn();
mColumn.ColumnName = "M";
this.SizeQuantityTable.Columns.Add(mColumn);
DataRow row1 = this.SizeQuantityTable.NewRow();
row1[sizeQuantityColumn] = "Blue";
row1[sColumn] = "12";
row1[mColumn] = "15";
this.SizeQuantityTable.Rows.Add(row1);
DataRow row2 = this.SizeQuantityTable.NewRow();
row2[sizeQuantityColumn] = "Red";
row2[sColumn] = "18";
row2[mColumn] = "21";
this.SizeQuantityTable.Rows.Add(row2);
DataRow row3 = this.SizeQuantityTable.NewRow();
row3[sizeQuantityColumn] = "Green";
row3[sColumn] = "24";
row3[mColumn] = "27";
this.SizeQuantityTable.Rows.Add(row3);
OK. I have created three columns namely sizeQuantityColumn, sColumn and mColumn and added three rows namely row1, row2 and row2.
So, Let's say I wanna set the selected item as row2 (So in the view, the second row should be highlighted).
How can I do this?
EDIT
I hardcoded the SelectedIndex of the DataGrid to 1. (So the second row should be selected). In design time it shows as selected. But not in the run time. You can see it in the below snapshot.
So ultimaltely the problem is Not highlighting the row.
There are a few way to select items in the DataGrid. It just depends which one works best for the situation
First and most basic is SelectedIndex this will just select the Row at that index in the DataGrid
<DataGrid SelectedIndex="{Binding SelectedIndex}" />
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
SelectedIndex = 2;
SelectedItem will select the row that matches the row you set
<DataGrid SelectedItem="{Binding SelectedRow}" />
private DataRow _selectedRow;
public DataRow SelectedRow
{
get { return _selectedRow; }
set { _selectedRow = value; NotifyPropertyChanged("SelectedRow");}
}
SelectedRow = items.First(x => x.whatever == something);
The most common one is SelectedValue with SelectedValuePath set, in this case you set the column you want to select with and then to can select the row by setting the corresponding value
<DataGrid SelectedValuePath="Size Quantity" SelectedValue="{Binding SelectionValue}"
private string _selectedValue
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
SelectionValue = "Blue";
Edit:
Here is my test and it is highlighting just fine
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.SizeQuantityTable = new DataTable();
DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
...................
........
}
private string _selectedValue;
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get { return sizeQuantityTable; }
set { sizeQuantityTable = value; NotifyPropertyChanged("SizeQuantityTable"); }
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
SelectedIndex = 2;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
SelectionValue = "Blue";
}
private void NotifyPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
Xaml:
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="202" Width="232" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<DataGrid SelectedValuePath="Size Quantity"
SelectedValue="{Binding SelectionValue}"
SelectedIndex="{Binding SelectedIndex}"
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,41" />
<StackPanel Orientation="Horizontal" Height="37" VerticalAlignment="Bottom" >
<Button Content="SelectedIndex" Height="26" Width="107" Click="Button_Click_1"/>
<Button Content="SelectedValue" Height="26" Width="107" Click="Button_Click_2"/>
</StackPanel>
</Grid>
</Window>
Result:
You can always use SelectedItem property and bind it against row, as such:
SelectedItem="{Binding ActiveRow}"
and in ViewModel do:
ActiveRow = secondRow;
Add SelectedItem, SelectedValue in your DataGrid.
<DataGrid
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
SelectedValue ="{Binding SelectedValue}"
Margin="0,0,0,120" />
And in your view model
private string _selectedValue;
public string SelectedValue
{
get
{
return _selectedValue;
}
set
{
_selectedValue = value;
NotifyPropertyChanged("SelectedValue");
}
}
private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable
{
get
{
return sizeQuantityTable;
}
set
{
sizeQuantityTable = value;
NotifyPropertyChanged("SizeQuantityTable");
}
}
You can use SelectedItem as well, that is preferred.
I just had the same problem.
I saw that the item of a datagrid was selected correctly at design time, but not at runtime. (By the way, I create the instance of the view model in the xaml).
I could solve this problem by moving the code to programmatically set the selected item from the view models constructor to a different method in the view model and then calling this method in the loaded event of the window (or usercontrol).
Obviously the view is not completely done initializing itself when the view models constructor is called.
This can be avoided by not coding in the view models constructor.
View (xaml):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels" Loaded="Window_Loaded">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
View (code behind):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
((ExampleViewModel)this.DataContext).LoadData();
}
If you do not like setting up the Loaded event in the code behind, you can also do it in xaml (references to "Microsoft.Expression.Interactions" and "System.Windows.Interactivity" are needed):
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="LoadData"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In each case, you call the LoadData method in the ViewModel:
public class ExampleViewModel
{
/// <summary>
/// Constructor.
/// </summary>
public ExampleViewModel()
{
// Do NOT set selected item here
}
public void LoadData()
{
// Set selected item here
}
For anyone using Observable Collections you may find this solution useful.
The SelectedModelIndex property simply returns the index of the SelectedModel from the ItemSource collection. I've found setting the SelectedIndex along with the SelectedItem highlights the row in the DataGrid.
private ObservableCollection<Model> _itemSource
public ObservableCollection<Model> ItemSource
{
get { return _itemSource; }
set
{
_itemSource = value;
OnPropertyChanged("ItemSource");
}
}
// Binding must be set to One-Way for read-only properties
public int SelectedModelIndex
{
get
{
if (ItemSource != null && ItemSource.Count > 0)
return ItemSource.IndexOf(SelectedModel);
else
return -1;
}
}
private Model _selectedModel;
public Model SelectedModel
{
get { return _selectedModel; }
set
{
_selectedModel = value;
OnPropertyChanged("SelectedModel");
OnPropertyChanged("SelectedModelIndex");
}
}
xaml:
<DataGrid SelectionUnit="FullRow" >
</DataGrid>
code
SelectRowByIndex(yourDataGridViewName, 0);
public static void SelectRowByIndex(DataGrid dataGrid, int rowIndex)
{
if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.FullRow))
throw new ArgumentException("The SelectionUnit of the DataGrid must be set to FullRow.");
if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
throw new ArgumentException(string.Format("{0} is an invalid row index.", rowIndex));
dataGrid.SelectedItems.Clear();
/* set the SelectedItem property */
object item = dataGrid.Items[rowIndex]; // = Product X
dataGrid.SelectedItem = item;
DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
if (row == null)
{
/* bring the data item (Product object) into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(item);
row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
}
//TODO: Retrieve and focus a DataGridCell object
if (row != null)
{
DataGridCell cell = GetCell(dataGrid, row, 0);
if (cell != null)
cell.Focus();
}
}
public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
{
if (rowContainer != null)
{
DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
if (presenter == null)
{
/* if the row has been virtualized away, call its ApplyTemplate() method
* to build its visual tree in order for the DataGridCellsPresenter
* and the DataGridCells to be created */
rowContainer.ApplyTemplate();
presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
}
if (presenter != null)
{
DataGridCell cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
if (cell == null)
{
/* bring the column into view
* in case it has been virtualized away */
dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
}
return cell;
}
}
return null;
}
public static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
foreach (childItem child in FindVisualChildren<childItem>(obj))
{
return child;
}
return null;
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj)
where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
I'm trying to implement column chooser functionality for a DataGrid and am running into problems if I try to define the content of the header for the column as something more than just a string. Below is a very simplified example with all styles, view models, binding, etc all stripped out.
There are 3 columns:
The first column uses a string for the header.
The second column tries to set the header content to a Label with a ToolTip.
The third column ties to set the header content to a TextBlock with a ToolTip.
Clicking the Toggle Visibility button for Column A works fine. The Toggle Visibility buttons for both Columns B and C cause an InvalidOperationException with the message "Specified element is already the logical child of another element. Disconnect it first."
<Window x:Class="DataGridColumnChoosing.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,10">
<TextBlock Margin="15, 0">Toggle Visibility:</TextBlock>
<Button Click="ToggleA">Column A</Button>
<Button Click="ToggleB">Column B</Button>
<Button Click="ToggleC">Column C</Button>
</StackPanel>
<!-- Main Fuel Mileage Datagrid -->
<DataGrid x:Name="mySampleDataGrid" Grid.Row="1"
AutoGenerateColumns="False" CanUserSortColumns="False" CanUserResizeRows="False" CanUserAddRows="False"
GridLinesVisibility="All" RowHeaderWidth="0">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="colA" Width="40*" IsReadOnly="True" Header="Column A">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="colB" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.Header>
<Label Content="Column B" ToolTip="A short explanation of Column B"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn x:Name="colC" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.Header>
<TextBlock Text="Column C" ToolTip="A short explanation of Column C " />
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
The simple click event handlers for the buttons that are toggling the visibility in this example are simply modifying the visibility of the columns.
private void ToggleA(object sender, RoutedEventArgs e)
{
colA.Visibility = colA.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
private void ToggleB(object sender, RoutedEventArgs e)
{
colB.Visibility = colB.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
private void ToggleC(object sender, RoutedEventArgs e)
{
colC.Visibility = colC.Visibility == System.Windows.Visibility.Visible ? System.Windows.Visibility.Hidden : System.Windows.Visibility.Visible;
}
Thanks all.
I had this issue once when I had a control defined in my Resources, and was trying to use it within multiple control's Content areas. That does not work because the control can only belong to one parent.
Instead, I needed to define a Template of some kind which contained the control I wanted, and set the Template of my object instead of the content directly.
Your comment on #Gimno's answer makes me think this is the case.
Try changing it so instead of setting a Label/TextBox in DataGrid.Header's content directly, set DataGrid.HeaderTemplate to a DataTemplate which contains the Label or TextBox.
EDIT
Here's some example code
<DataGridTemplateColumn x:Name="colB" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<Label Content="Column B" ToolTip="A short explanation of Column B"/>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I think it would be easiest if you just use DataGridTemplateColumn.HeaderStyle instead of DataGridTemplateColumn.Header
As example for column c:
<DataGridTemplateColumn x:Name="colC" Width="40*" IsReadOnly="True" >
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Column C" ToolTip="A short explanation of Column C "/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGridTemplateColumn.HeaderStyle>
I liked the column chooser solution from CodePlex:
DataGrid Behavior
I clean up the code and remove unnecessary code:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Microsoft.Xaml.Behaviors;
namespace Behaviors
{
public class WpfDataGridConfigurationBehavior : Behavior<DependencyObject>
{
#region "public Properties"
public bool DontPersistVisibleColumns { get; set; }
public bool DontPersistColumnsOrder { get; set; }
public string ContextMenuChoices { get; set; } = "Alphabetical Menu,Show All Columns";
public string DataGridName { get; set; }
#endregion "public Properties"
#region "private Properties"
private DataGrid dataGrid;
private ContextMenu theContextMenu; // Context Menu for the field chooser.
private string AllColumnsHeaders { get; set; }
private string AllColumnDisplayIndexes { get; set; }
private int nBaseItems = 5;
private MenuItem mnuAlpha;
private MenuItem mnuShowAll;
#endregion "Private Properties"
protected override void OnAttached()
{
base.OnAttached();
dataGrid = this.AssociatedObject as DataGrid;
if (DataGridName == null) DataGridName = dataGrid.Name;
ContextMenu_BuildStaticMenu();
dataGrid.Loaded += (sender, e) => { DataGrid_Loaded(); };
dataGrid.AutoGeneratedColumns += DataGrid_AutoGeneratedColumns;
dataGrid.ColumnReordered += (sender, e) => { DataGrid_ColumnReordered(sender); };
dataGrid.ContextMenuOpening += (sender, e) => { DataGrid_ContextMenuOpening(e); };
dataGrid.LoadingRow += (sender, e) => { e.Row.Header = (e.Row.GetIndex() + 1).ToString(); };
dataGrid.RowHeaderWidth = 0;
var ns = new Style(typeof(DataGridRowHeader)) { BasedOn = dataGrid.RowHeaderStyle };
ns.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.Bold));
ns.Setters.Add(new Setter(Control.FontSizeProperty, 11.0));
ns.Setters.Add(new Setter(Control.PaddingProperty, new Thickness(4, 0, 4, 0)));
dataGrid.RowHeaderStyle = ns;
}
private void DataGrid_AutoGeneratedColumns(object sender, EventArgs e)
{
DataGrid_Loaded();
}
#region "DataGrid Events"
private void DataGrid_ContextMenuOpening(ContextMenuEventArgs e)
{
var dep = (DependencyObject)e.OriginalSource;
// iteratively traverse the visual tree
while ((dep != null) && !(dep is DataGridCell) && !(dep is DataGridColumnHeader))
dep = VisualTreeHelper.GetParent(dep);
if (dep == null)
return;
if (dep is DataGridColumnHeader)
{
// do something
}
if (dep is DataGridCell)
{
// navigate further up the tree
while ((dep != null) && !(dep is DataGridRow))
dep = VisualTreeHelper.GetParent(dep);
var row = dep as DataGridRow;
dataGrid.ItemContainerGenerator.IndexFromContainer(row);
}
}
private void DataGrid_ColumnReordered(object sender)
{
Settings_SaveDisplayIndexes(sender);
ContextMenu_BuildMenu();
}
private void DataGrid_Loaded()
{
ContextMenu_BuildMenu(false);
VisibleColumns_Initialize();
}
#endregion "DataGrid Events"
#region "ContextMenu Methods and Events"
private void ContextMenu_BuildStaticMenu()
{
theContextMenu = new ContextMenu { FontSize = 11, StaysOpen = true };
mnuAlpha = new MenuItem
{
Header = ContextMenuChoices.Split(',')[0],
FontWeight = FontWeights.Bold,
IsCheckable = true,
StaysOpenOnClick = true
};
mnuAlpha.Click += (sender, e) => { ContextMenu_BuildMenu(); };
mnuShowAll = new MenuItem
{
Header = ContextMenuChoices.Split(',')[1],
FontWeight = FontWeights.Bold,
IsCheckable = true,
StaysOpenOnClick = true
};
mnuShowAll.Checked += (sender, e) => { VisibleColumns = AllColumnsHeaders; };
mnuShowAll.Click += (sender, e) =>
{
if (mnuShowAll.IsChecked == false) VisibleColumns_Initialize();
};
theContextMenu.Items.Add(mnuShowAll);
theContextMenu.Items.Add(mnuAlpha);
}
private void ContextMenu_BuildMenu(bool pbRebuild = true)
{
for (int i = theContextMenu.Items.Count - 1; i > 0; i--)
if (((MenuItem)theContextMenu.Items[i]).FontWeight != FontWeights.Bold)
theContextMenu.Items.Remove(theContextMenu.Items[i]);
nBaseItems = theContextMenu.Items.Count;
// Attach the context menu to the DataGrid ColumnHeaders
var headersPresenter = WpfDataGridConfigurationBehaviorFinder.FindChild<DataGridColumnHeadersPresenter>(dataGrid);
ContextMenuService.SetContextMenu(headersPresenter, theContextMenu);
if (VisibleColumns == null)
throw (new SettingsPropertyNotFoundException("User's VisibleColumns setting not found."));
// Get the current column ordering from user.config
if (DisplayIndexes == null)
throw (new SettingsPropertyNotFoundException("User's DisplayIndexes setting not found."));
AllColumnDisplayIndexes = DisplayIndexes;
string[] colIndexes = AllColumnDisplayIndexes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var dataGridColumns = dataGrid.Columns.OrderBy(x => x.DisplayIndex);
// Sort the columns in display index order so menu header order matchs display column order
if (pbRebuild == false)
// Initially the datagrid column order is such that display indexes are the same as the col indexes
if (colIndexes.Length > 0)
dataGridColumns = dataGrid.Columns.OrderBy(x => Convert.ToInt16(colIndexes[x.DisplayIndex]));
if (mnuAlpha.IsChecked)
dataGridColumns = dataGrid.Columns.OrderBy(x => x.Header.ToString());
AllColumnsHeaders = "";
foreach (var col in dataGridColumns)
{
// All column name to a list of all column headers for later use.
AllColumnsHeaders = $"{col.Header.ToString().Replace("\n", " ").Replace("\r", " ")};{AllColumnsHeaders}";
// Add new menu item in display order.
ContextMenu_AddNewMenuItem(col);
}
string sTemp = VisibleColumns;
VisibleColumns = null;
VisibleColumns = sTemp;
}
private void ContextMenu_AddNewMenuItem(DataGridColumn col)
{
var menuItem = new MenuItem { Header = col.Header.ToString().Replace("\n", " ").Replace("\r", " "), StaysOpenOnClick = true };
var saVisibleColumns = new List<string> { string.Empty };
if (VisibleColumns != null)
{
saVisibleColumns = VisibleColumns.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
}
menuItem.IsChecked = (saVisibleColumns.Contains(menuItem.Header));
menuItem.Click += (sender, e) => { ContextMenu_ColumnName_Click(sender); };
theContextMenu.Items.Add(menuItem);
}
private void ContextMenu_ColumnName_Click(object sender)
{
var mi = sender as MenuItem;
// Get the column name that was clicked
string colName = mi.Header.ToString();
// Capture new visible columns list
Settings_SaveVisibleColumns(mi, colName);
}
#endregion "ContextMenu Methods and Events"
#region "Settings Methods"
private void Settings_SaveVisibleColumns(MenuItem mi, string colName)
{
if (theContextMenu.Items.Count - nBaseItems < dataGrid.Columns.Count)
return;
// Put the visible column names into an array
var saVisibleColumns = VisibleColumns.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();
if (saVisibleColumns != null && saVisibleColumns.Count > 0 &&
saVisibleColumns[saVisibleColumns.Count - 1].StartsWith("-"))
saVisibleColumns.RemoveAt(saVisibleColumns.Count - 1);
// If the menu item is unchecked (column is not visible)
if (!mi.IsChecked)
// Make the column visible by adding its name to the Visible Columns list
saVisibleColumns.Add(colName);
else
// Hide the column by removing its name from the VisibleColumns list
if (saVisibleColumns.Contains(colName) && saVisibleColumns.Count > 1)
saVisibleColumns.Remove(colName);
VisibleColumns = string.Join(";", saVisibleColumns) + ";";
}
private void Settings_SaveDisplayIndexes(object sender)
{
// Capture the new column order
AllColumnDisplayIndexes = "";
foreach (DataGridColumn col in ((DataGrid)sender).Columns)
{
AllColumnDisplayIndexes +=
(AllColumnDisplayIndexes.Length > 0 ? ";" : "") + col.DisplayIndex;
}
DisplayIndexes = AllColumnDisplayIndexes;
}
#endregion "Settings Methods"
#region DisplayIndexes (DependencyProperty)
public string DisplayIndexes
{
get { return (string)GetValue(DisplayIndexesProperty); }
set { SetValue(DisplayIndexesProperty, value); }
}
public static readonly DependencyProperty DisplayIndexesProperty =
DependencyProperty.Register("DisplayIndexes", typeof(string), typeof(WpfDataGridConfigurationBehavior), new PropertyMetadata("", OnDisplayIndexesChanged));
private static void OnDisplayIndexesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((WpfDataGridConfigurationBehavior)d).DisplayIndexesChanged(e);
}
public void DisplayIndexesChanged(DependencyPropertyChangedEventArgs e)
{
// Persist the new column order
if (dataGrid != null && !DontPersistColumnsOrder)
Settings_Save(DataGridName + "DisplayIndexes", AllColumnDisplayIndexes);
}
#endregion "DisplayIndexes (DependencyProperty)"
#region VisibleColumns (DependencyProperty)
/// <summary>
///
/// Gets or sets a value indicating the names of columns
/// (as they appear in the column header) to be visible, seperated by a semicolon.
///
/// Columns whose names are not here will be hidden.
/// </summary>
public string VisibleColumns
{
get { return (string)GetValue(VisibleColumnsProperty); }
set { SetValue(VisibleColumnsProperty, value); }
}
private void VisibleColumns_Initialize()
{
// Get saved VisibleColumns from app.config
// Initialize VisibleColumns
VisibleColumns = string.IsNullOrEmpty(VisibleColumns.Replace("\n", " ").Replace("\r", " ")) ? AllColumnsHeaders : VisibleColumns;
}
public static readonly DependencyProperty VisibleColumnsProperty =
DependencyProperty.Register("VisibleColumns", typeof(string), typeof(WpfDataGridConfigurationBehavior), new PropertyMetadata("", OnVisibleColumnsChanged));
private static void OnVisibleColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((WpfDataGridConfigurationBehavior)d).VisibleColumnsChanged(e);
}
/// <summary>
///
/// Updates the display
///
/// </summary>
/// <param name="e"></param>
public void VisibleColumnsChanged(DependencyPropertyChangedEventArgs e)
{
if (theContextMenu == null)
return;
if (e.NewValue != null)
{
var showTheseColumns = e.NewValue.ToString().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var saContextMenuVisibleItems = new List<string>();
int iCol = 0;
foreach (MenuItem menuItem in theContextMenu.Items)
{
// Show/Hide the Context Menu item's checkmark.
if (menuItem.FontWeight == FontWeights.Bold) continue;
menuItem.IsChecked = showTheseColumns.Contains(menuItem.Header.ToString());
if (menuItem.IsChecked) saContextMenuVisibleItems.Add(menuItem.Header.ToString());
mnuShowAll.IsChecked = mnuShowAll.IsChecked && menuItem.IsChecked;
// Assign menu item's column's DisplayIndex in display order, (i.e. in menu item order), looking up each column by header name.)
if (mnuAlpha.IsChecked == false)
dataGrid.Columns.First(x => x.Header.ToString().Replace("\n", " ").Replace("\r", " ") == menuItem.Header.ToString()).DisplayIndex = iCol++;
}
// Show the columns
foreach (var col in dataGrid.Columns)
col.Visibility =
showTheseColumns.Contains(col.Header.ToString().Replace("\n", " ").Replace("\r", " "))
&& (saContextMenuVisibleItems.Contains(col.Header.ToString().Replace("\n", " ")
.Replace("\r", " ")))
? Visibility.Visible
: Visibility.Collapsed;
// Persist the new visible columns list
if (dataGrid != null && !DontPersistVisibleColumns)
Settings_Save(DataGridName + "VisibleColumns", VisibleColumns);
}
}
#endregion "VisibleColumns"
public static void Settings_Save(string propertyName, string propertyValue)
{
foreach (SettingsPropertyValue property in Settings.Default.PropertyValues)
{
if (propertyName == property.Name)
{
property.PropertyValue = propertyValue;
Settings.Default.Save();
}
}
}
}
public static class WpfDataGridConfigurationBehaviorFinder
{
public static T FindChild<T>(DependencyObject depObj) where T : DependencyObject
{
// Confirm obj is valid.
if (depObj == null) return null;
// success case
if (depObj is T) return depObj as T;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
T obj = FindChild<T>(VisualTreeHelper.GetChild(depObj, i));
if (obj != null) return obj;
}
return null;
}
public interface IBreakVisualParenting
{
DependencyObject Parent { get; }
}
public static T LastVisualAncestorOfType<T>(this DependencyObject element) where T : DependencyObject
{
T item = null;
var parent = VisualTreeHelper.GetParent(element);
while (parent != null)
{
if (parent is T)
item = (T)parent;
if (parent is IBreakVisualParenting)
{
parent = ((IBreakVisualParenting)parent).Parent;
}
else
parent = VisualTreeHelper.GetParent(parent);
}
return item;
}
}
}
I am using a MVVM Wizard with several pages. When I set a value in the combobox and go to the next page and switch back I want to reset the value I set before.
But all whats happening is that the combobox is empty at top and the index is -1 ?
What do I wrong?
<ComboBox ItemsSource="{Binding Path=LessonNumbers}" SelectedIndex="{Binding SelectedLessonNumber}" />
private ReadOnlyCollection<int> _lessonNumbers;
public ReadOnlyCollection<int> LessonNumbers
{
get
{
if (_lessonNumbers == null)
this.CreateLessonNumbers();
return _lessonNumbers;
}
}
private void CreateLessonNumbers()
{
var list = new List<int>();
for (int i = 1; i < 24; i++)
{
list.Add(i);
}
_lessonNumbers = new ReadOnlyCollection<int>(list);
}
private int _selectedLessonNumber;
public int SelectedLessonNumber
{
get { return _selectedLessonNumber; }
set
{
if (_selectedLessonNumber == value)
return;
_selectedLessonNumber = value;
this.OnPropertyChanged("SelectedLessonNumber");
}
}
UPDATE:
<ComboBox
SelectedIndex="0"
SelectedItem="{Binding SelectedWeeklyRotationNumber}"
ItemsSource="{Binding Path=WeeklyRotationNumbers}"
Height="23"
HorizontalAlignment="Left"
Margin="336,212,0,0"
VerticalAlignment="Top"
Width="121"
MaxDropDownHeight="100"
IsReadOnly="True"
IsTextSearchEnabled="False"
/>
private ReadOnlyCollection _weeklyRotationNumbers;
public ReadOnlyCollection WeeklyRotationNumbers
{
get
{
if (_weeklyRotationNumbers == null)
this.CreateWeeklyRotationNumbers();
return _weeklyRotationNumbers;
}
}
private void CreateWeeklyRotationNumbers()
{
var list = new List<string>();
list.Add("No rotation");
for (int i = 1; i < 16; i++)
list.Add(i.ToString());
_weeklyRotationNumbers = new ReadOnlyCollection<string>(list);
}
private string _selectedWeeklyRotationNumber;
public string SelectedWeeklyRotationNumber
{
get { return _selectedWeeklyRotationNumber; }
set
{
if (_selectedWeeklyRotationNumber == value)
return;
_selectedWeeklyRotationNumber = value;
this.RaisePropertyChanged("SelectedWeeklyRotationNumber");
Messenger.Default.Send<string>(value);
}
}
Again, what do I wrong or what is wrong with the string property?
Change XAML SelectedIndex to SelectedItem:
<ComboBox ItemsSource="{Binding Path=LessonNumbers}"
SelectedItem="{Binding SelectedLessonNumber}" />
UPDATE:
Somewhere you must set the DataContext of your Window to reference the collection from your XAML.
In my case I typically do that in the constructor of my view.
// this my class containing WeeklyRotationNumbers
private MainViewModel _mvm;
public MainView()
{
InitializeComponent();
_mvm = new MainViewModel();
DataContext = _mvm;
}
I added string to the read only collections:
private ReadOnlyCollection<string> _weeklyRotationNumbers;
public ReadOnlyCollection<string> WeeklyRotationNumbers
I also implemented the interface INotifyPropertyChanged which I think you did, but you are likely using
a different base class to handle the PropertyChanged event.
Everthing else I cut and paste from your code.
I have a treeview which shows a hierarchy of items where each item has a checkbox. I would like to show below the treeview a listbox with all the checked items. How to achieve such functionality using MVVM pattern?
Thanks in advance
Lukasz Glaz
Here's an example:
ViewModel
public class TreeNodeViewModel : ViewModelBase
{
#region Constructors
public TreeNodeViewModel(string text, params TreeNodeViewModel[] nodes)
: this(text, new ObservableCollection<TreeNodeViewModel>(nodes))
{
}
public TreeNodeViewModel(string text, ObservableCollection<TreeNodeViewModel> nodes)
{
Text = text;
Nodes = nodes;
foreach (var node in Nodes)
{
node.Parent = this;
}
Nodes.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Nodes_CollectionChanged);
}
#endregion
#region Private methods
private void Nodes_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (var node in e.OldItems.Cast<TreeNodeViewModel>())
{
node.Parent = null;
}
foreach (var node in e.NewItems.Cast<TreeNodeViewModel>())
{
node.Parent = this;
}
OnPropertyChanged("CheckedNodes");
}
private void NotifyParent()
{
if (Parent != null)
{
Parent.OnPropertyChanged("CheckedNodes");
Parent.NotifyParent();
}
}
#endregion
#region Private data
private string _text;
private bool _isChecked;
private TreeNodeViewModel _parent;
#endregion
#region Public properties
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
OnPropertyChanged("Text");
}
}
}
public bool IsChecked
{
get { return _isChecked; }
set
{
if (value != _isChecked)
{
_isChecked = value;
NotifyParent();
OnPropertyChanged("IsChecked");
}
}
}
public ObservableCollection<TreeNodeViewModel> Nodes { get; private set; }
public IEnumerable<TreeNodeViewModel> CheckedNodes
{
get
{
foreach (var node in Nodes)
{
if (node.IsChecked)
yield return node;
foreach (var child in node.CheckedNodes)
{
yield return child;
}
}
}
}
public TreeNodeViewModel Parent
{
get { return _parent; }
private set
{
if (value != _parent)
{
_parent = value;
OnPropertyChanged("Parent");
}
}
}
#endregion
}
XAML
<TreeView Grid.Row="0" ItemsSource="{Binding Nodes}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<ListBox Grid.Row="1" ItemsSource="{Binding CheckedNodes}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code-behind
this.DataContext = new TreeNodeViewModel(
null,
new TreeNodeViewModel(
"1",
new TreeNodeViewModel(
"1.1",
new TreeNodeViewModel("1.1.1"),
new TreeNodeViewModel("1.1.2")),
new TreeNodeViewModel("1.2")),
new TreeNodeViewModel(
"2",
new TreeNodeViewModel("2.1"),
new TreeNodeViewModel(
"2.2",
new TreeNodeViewModel("2.2.1"))));
Note that it is a rather naive implementation, it could easily be improved... For instance, the whole list of checked nodes is reevaluated every time a node is checked/unchecked, which could lead to bad performance for a big TreeView