Combobox and deleted record - c#

I have 2 related tables - CarMarks and CarMarkGroups. Any CarMark has CarMarkGroupID. I have edit form for CarMarks with combobox binding to CarMarkGroups.
I mark one of CarMarkGroup as deleted record. So, this marked CarMarkGroup is not in list binding to combobox (because I must show only active CarMarkGroups). But CarMarkGroupID in selected CarMark still links to marked as deleted CarMarkGroup. And i dont need to change this link.
But I need show this record for selected CarMark even if it was marked for deletion. What is the best practice for that? Need I build list for combobox in ChangeSelection Event? Or what?

I'd propose a solution, based on ICollectionView filtering.
View models:
public class GroupViewModel
{
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
public class ItemViewModel
{
public ItemViewModel(List<GroupViewModel> groups)
{
Groups = new ListCollectionView(groups)
{
Filter = g =>
{
var group = (GroupViewModel)g;
return !group.IsDeleted || (Group == group);
}
};
}
public string Name { get; set; }
public GroupViewModel Group
{
get { return group; }
set
{
if (group != value)
{
group = value;
Groups.Refresh();
}
}
}
private GroupViewModel group;
public ICollectionView Groups { get; private set; }
}
public class ListViewModel
{
private readonly List<GroupViewModel> groups;
public ListViewModel()
{
// some sample initialization
groups = new List<GroupViewModel>
{
new GroupViewModel { Name = "Friuts" },
new GroupViewModel { Name = "Animals" },
new GroupViewModel { Name = "Trees", IsDeleted = true }
};
Items = new List<ItemViewModel>
{
new ItemViewModel(groups) { Name = "Orange", Group = groups[0] },
new ItemViewModel(groups) { Name = "Apple", Group = groups[0] },
new ItemViewModel(groups) { Name = "Banana", Group = groups[0] },
new ItemViewModel(groups) { Name = "Cat", Group = groups[1] },
new ItemViewModel(groups) { Name = "Dog", Group = groups[1] },
new ItemViewModel(groups) { Name = "Bird", Group = groups[1] },
new ItemViewModel(groups) { Name = "Oak", Group = groups[2] },
new ItemViewModel(groups) { Name = "Nut-tree", Group = groups[2] },
new ItemViewModel(groups) { Name = "Pine", Group = groups[2] },
};
}
public IEnumerable<ItemViewModel> Items { get; private set; }
}
View (it's a Window content in WPF Application project):
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<DataGrid x:Name="ItemsGrid" AutoGenerateColumns="False" ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Group" Binding="{Binding Group.Name}"/>
</DataGrid.Columns>
</DataGrid>
<ContentControl Grid.Column="1" Content="{Binding SelectedItem, ElementName=ItemsGrid}">
<ContentControl.ContentTemplate>
<DataTemplate>
<ComboBox Height="20" ItemsSource="{Binding Groups}" SelectedItem="{Binding Group}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ListViewModel();
}
}
So, when item is selected, ComboBox contains all non-deleted groups PLUS group of selected item, if this group is deleted.

I am not sure i understand the connections exactly but if you still need to see that CarMarkGroup in your CarMarks ComboBox, maybe add a boolean flag in your CarMarkGroup, something like ProposedForDeletion and when that changes to true, you change some color or another element in the GUI to let the user now it was proposed for deletion, or even display a message regarding this somewhere. Do not delete it from the ObservableCollection but just mark its flag to true and keep it there and do your further check according to this new flag.

Related

How to fix the missing display of rows in a DataGrid, when the columns are dynamically generated?

In my current WPF application, it's required to generate columns of a DataGrid dynamically in my ViewModel. For that I have used the approach with System.Windows.Interactivity and behaviors.
Therefore I created ColumnsBehavior exactly like in this post:
Is it possible to get dynamic columns on wpf datagrid in mvvm pattern?
Everything worked fine, when I initialized the bounded list and generated the columns with the bindings.
When I switched to ListBox to show a DataGrid in every ListBoxItem, everything worked great again.
Now I want to add the information (model) to the bounded list at run time.
When a ListBoxItem is selected, the corresponding DataGrid should be displayed. With a Button above the DataGrid I can pass sample data to the bounded list objects. When I hit the Button, the right amount of columns with the right headers are generated, but the rows only appears the next time, in which the ListBoxItem is visible again. This means the data is appearing as recently as I refresh the grid.
To bind the Botton Command property to the ViewModel I used the ActionCommand implementation:
RelayCommand
I am using the MVVM pattern.
This is my ViewModel:
public class ViewModel
{
public ViewModel()
{
ListItems = new ObservableCollection<ListItemModel>();
ListItems.Add(new ListItemModel()
{
IsSelected = true
});
ListItems.Add(new ListItemModel()
{
IsSelected = false
});
FillCommand = new RelayCommand(FillAction);
}
public ObservableCollection<ListItemModel> ListItems { get; set; }
public ListItemModel SelectedListItem { get; set; }
public ICommand FillCommand { get; set; }
public void FillAction(object sender)
{
SelectedListItem.Entries = new ObservableCollection<Entry>()
{
new Entry()
{
Cells = new ObservableCollection<Cell>()
{
new Cell() { Cond1 = 11, Value = 99 },
new Cell() { Cond1 = 22, Value = 99 },
new Cell() { Cond1 = 33, Value = 99 }
}
},
new Entry()
{
Cells = new ObservableCollection<Cell>()
{
new Cell() { Cond1 = 11, Value = 99 },
new Cell() { Cond1 = 22, Value = 99 },
new Cell() { Cond1 = 33, Value = 99 }
},
},
new Entry()
{
Cells = new ObservableCollection<Cell>()
{
new Cell() { Cond1 = 11, Value = 99 },
new Cell() { Cond1 = 22, Value = 99 },
new Cell() { Cond1 = 33, Value = 99 }
},
}
};
SelectedListItem.GenerateGrid();
}
}
The property for holding the columns is in the ListItemModel.
The object structure behind the ItemsSource of the DataGrid and the columns property you can see in the next code:
public class ListItemModel
{
public ListItemModel()
{
Entries = new ObservableCollection<Entry>();
DataColumns = new ObservableCollection<DataGridColumn>();
}
public ObservableCollection<Entry> Entries { get; set; }
public ObservableCollection<DataGridColumn> DataColumns { get; set; }
public bool IsSelected { get; set; }
public void GenerateGrid()
{
if (Entries.Count != 0)
{
var columns = Entries[0]?.Cells;
for (int i = 0; i < columns.Count; i++)
{
Binding b = new Binding(string.Format("Cells[{0}].Value", i));
DataGridTextColumn text = new DataGridTextColumn();
text.Header = columns[i].Cond1.ToString();
text.Binding = b;
DataColumns.Add(text);
}
}
}
}
public class Entry
{
public ObservableCollection<Cell> Cells { get; set; }
public Entry() { Cells = new ObservableCollection<Cell>(); }
}
public class Cell
{
public Cell() { }
public int Cond1 { get; set; }
public int Value { get; set; }
}
}
For the view you need the namespace:
http://schemas.microsoft.com/expression/2010/interactivity
The following code sample shows the view.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding ListItems}" SelectedItem="{Binding SelectedListItem}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
</ListBox>
<DockPanel Grid.Column="1">
<Button DockPanel.Dock="Top" Command="{Binding FillCommand}">Fill Data</Button>
<DataGrid x:Name="ToleranceGrid" ColumnWidth="*" ItemsSource="{Binding Path=SelectedListItem.Entries}"
CanUserAddRows="False" SelectionMode="Single" SelectionUnit="Cell" AutoGenerateColumns="False" local:ColumnsBindingBehaviour.BindableColumns="{Binding Path=SelectedListItem.DataColumns}">
</DataGrid>
</DockPanel>
</Grid>
The ViewModel is set to the DataContext of the view in code behind.
The result should look like this:
WPF Application
The first ListItemModel is selected and I pressed the Button above the DataGrid. As you can see, the columns were generated, but the rows aren't displayed. I debugged the code, an everything is correctly set in my ViewModel. When I would select the second ListItemModel and would go back to the first one, the content will be shown correctly.
Do you have any ideas?
Your ListItemModel should implement the INotifyPropertyChanged interface and raise the PropertyChanged event when the Entries property is set to a new collection:
public class ListItemModel : INotifyPropertyChanged
{
public ListItemModel()
{
Entries = new ObservableCollection<Entry>();
DataColumns = new ObservableCollection<DataGridColumn>();
}
private ObservableCollection<Entry> _entries;
public ObservableCollection<Entry> Entries
{
get { return _entries; }
set { _entries = value; NotifyPropertyChanged(); }
}
public ObservableCollection<DataGridColumn> DataColumns { get; set; }
public bool IsSelected { get; set; }
public void GenerateGrid()
{
if (Entries.Count != 0)
{
var columns = Entries[0]?.Cells;
for (int i = 0; i < columns.Count; i++)
{
Binding b = new Binding(string.Format("Cells[{0}].Value", i));
DataGridTextColumn text = new DataGridTextColumn();
text.Header = columns[i].Cond1.ToString();
text.Binding = b;
DataColumns.Add(text);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Wpf Depended combobox doesn't dispaly SelectedItem in .Net 4.5

I have main combobox (Categories) and depended combobox (Subcategories). I want it to display SelectedItems when window opens. All works fine in .Net 4.0, but it doesn't work in .Net 4.5. I have two computeres with these .Net versions.
In .net 4.5. only main combobox displays SelectedItem, depended doesn't. How can I fix it?
I made test project to all of you who're interested, just copy and paste. I have no idea how I can make it smaller, sry. But it is simple, clear code example 100% generates the problem.
XAML:
<Window x:Class="GridTest.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converter="clr-namespace:GridTest"
Title="TestWindow"
Height="300"
Width="300">
<Window.Resources>
<Converter:CategoryConverter x:Key="CategoryConverter"/>
</Window.Resources>
<Grid>
<DataGrid Name="_dataGrid"
CanUserAddRows="False"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0"
Name="_categories"
ItemsSource="{Binding Categories}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCategory, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</ComboBox>
<ComboBox Grid.Column="1"
SelectedItem="{Binding SelectedSubcategory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name">
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource CategoryConverter}">
<Binding Path="Subcategories"/>
<Binding Path="SelectedItem"
ElementName="_categories"/>
</MultiBinding>
</ComboBox.ItemsSource>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code:
public class CategoryConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] == null) return null;
var subcategories = values[0] as List<Subcategory>;
if (subcategories == null) return null;
var category = values[1] as Category;
if (category == null) return subcategories;
return subcategories.Where(g => g.CategoryId == category.Id);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public enum CategoryKinds
{
Car = 0,
Fruit = 1,
}
public class Category
{
public Int32 Id { get; set; }
public String Name { get; set; }
public override Boolean Equals(object obj)
{
var c = obj as Category;
if (c == null) return false;
return Id == c.Id;
}
}
public class Subcategory
{
public Int32 Id { get; set; }
public String Name { get; set; }
public Int32 CategoryId { get; set; }
public override Boolean Equals(object obj)
{
var sc = obj as Subcategory;
if (sc == null) return false;
return Id == sc.Id;
}
}
public class DataGridItem
{
public List<Category> Categories { get; set; }
public Category SelectedCategory { get; set; }
public List<Subcategory> Subcategories { get; set; }
public Subcategory SelectedSubcategory { get; set; }
public DataGridItem()
{
Categories = new List<Category>
{
new Category
{
Id = (Int32)CategoryKinds.Car, Name = "Car"
},
new Category
{
Id = (Int32)CategoryKinds.Fruit, Name = "Fruit"
}
};
Subcategories = new List<Subcategory>
{
new Subcategory
{
Id = 1,
Name = "Volvo",
CategoryId = (Int32) CategoryKinds.Car
},
new Subcategory
{
Id = 2,
Name = "Nissan",
CategoryId = (Int32) CategoryKinds.Car
},
new Subcategory
{
Id = 3,
Name = "Banana",
CategoryId = (Int32)CategoryKinds.Fruit
},
new Subcategory
{
Id = 4,
Name = "Lemon",
CategoryId = (Int32)CategoryKinds.Fruit
},
};
}
}
/// <summary>
/// Interaction logic for TestWindow.xaml
/// </summary>
public partial class TestWindow : Window
{
public List<DataGridItem> GridItems { get; set; }
public TestWindow()
{
InitializeComponent();
DataContext = this;
GridItems = new List<DataGridItem>
{
new DataGridItem
{
SelectedCategory = new Category
{
Id = (Int32)CategoryKinds.Car, Name = "Car"
},
SelectedSubcategory = new Subcategory
{
Id = 2,
Name = "Nissan",
CategoryId = (Int32) CategoryKinds.Car
}
},
new DataGridItem
{
SelectedCategory = new Category
{
Id = (Int32)CategoryKinds.Fruit, Name = "Fruit"
},
SelectedSubcategory = new Subcategory
{
Id = 4,
Name = "Lemon",
CategoryId = (Int32) CategoryKinds.Car
}
}
};
_dataGrid.ItemsSource = GridItems;
}
}
UPDATE
With approach suggested by Ilan and charly_b code will work fine.
GridItems = new List<DataGridItem>
{
new DataGridItem(),
new DataGridItem()
};
GridItems[1].SelectedCategory = GridItems[1].Categories[0];
GridItems[1].SelectedSubcategory = GridItems[1].Subcategories[1];
GridItems[0].SelectedCategory = GridItems[0].Categories[1];
GridItems[0].SelectedSubcategory = GridItems[0].Subcategories[3];
This code will result to:
Fruit - Lemon
Car - Nissan
But I have solution that will work even if you set SelectedItem that don't belong to ItemsSource of Combobox. You can override GetHashCode method like this:
public override int GetHashCode()
{
return Name.GetHashCode();
}
Obviously, in .Net 4.5 some of WPF methods operating with searching SelectedItem in Combobox's ItemsSource have different implementation from .Net 4.0 and now they use GetHashCode method :)
Try the next changes, the best practice is to use the source collection items in order to define the selected item. Firstly it is an architectural error to use a new item to define the selection (in both 4.5 and 4 dot.net versions). And second I advice you to use the mvvm approach (including INotifyPropertyChange implementation) to develop wpf related applications, and then all selection logic have to be moved to ViewModel and separated from the code behind (xaml.cs files).
public MainWindow()
{
InitializeComponent();
DataContext = this;
var f = new DataGridItem();
var firstselectedCategory = f.Categories.FirstOrDefault();
if (firstselectedCategory != null)
{
f.SelectedCategory = firstselectedCategory;
f.SelectedSubcategory =
f.Subcategories.FirstOrDefault(subcategory => subcategory.CategoryId == firstselectedCategory.Id);
}
else
{
f.SelectedCategory = null;
f.SelectedSubcategory = null;
}
var s = new DataGridItem();
var secondSelectedCategory = s.Categories.FirstOrDefault(category => !Equals(category, f.SelectedCategory));
if (secondSelectedCategory != null)
{
s.SelectedCategory = secondSelectedCategory;
s.SelectedSubcategory =
s.Subcategories.FirstOrDefault(subcategory => subcategory.CategoryId == secondSelectedCategory.Id);
}
else
{
s.SelectedCategory = null;
s.SelectedSubcategory = null;
}
GridItems = new List<DataGridItem>
{
f,s,
};
#region
//GridItems = new List<DataGridItem>
//{
// new DataGridItem
// {
// SelectedCategory = new Category
// {
// Id = (Int32) CategoryKinds.Car,
// Name = "Car"
// },
// SelectedSubcategory = new Subcategory
// {
// Id = 2,
// Name = "Nissan",
// CategoryId = (Int32) CategoryKinds.Car
// }
// },
// new DataGridItem
// {
// SelectedCategory = new Category
// {
// Id = (Int32) CategoryKinds.Fruit,
// Name = "Fruit"
// },
// SelectedSubcategory = new Subcategory
// {
// Id = 4,
// Name = "Lemon",
// CategoryId = (Int32) CategoryKinds.Fruit
// }
// }
//};
#endregion
_dataGrid.ItemsSource = GridItems;
}
The xaml code was not changed.
How it looks like:
.
I'll be glad to help if will have problems with the code.
Regards.
The Combobox SelectedItem object must be contained inside the Combobox's ItemsSource List.
In order to make your Programm work you can replace the SelectedSubCategory Property with the following code: (I would not use it like this in the production code, but it demonstrates how it works)
private Subcategory SelectedSubcategoryM;
public Subcategory SelectedSubcategory
{
get
{
return this.SelectedSubcategoryM;
}
set
{
this.SelectedSubcategoryM = (from aTest in this.Subcategories
where aTest.Id == value.Id
select aTest).Single();
}
}

Bind ListView selected item to a DataGrid

I have a ListView that holds a list of names, each name comes from a binding to an object containing a "name" field, like this:
public class User {
public User() {}
public User(string name, int ID) {
this.name = name;
this.ID = ID;
}
public string name {set; get;}
public int ID {set; get;}
}
and the listView:
<ListView Margin="10" Name="lvName" DisplayMemberPath="name"/>
and in the code behind I set lvName.ItemSource to an ObservableCollection of User objects, in the collection I have Users that can have the same Name, but not the same ID.
I also have a dataGrid, with 2 columns defined(one for name and one for ID), I want to make it so that when the user selects a row from the listview(which shows names of Users), the datagrid would be populated with all the User objects(both the name and ID) that have the same name as was selected in the listview.
how can I do that?
Note: I managed to bind the name field to a textblock, by doing this:
Text="{Binding SelectedItem.name, ElementName=lvName}"
but I have no idea how to do it on a datagrid, let alone with the ID field as well as the name field.
This could be easily done using MVVM approach.
Assuming, that User class looks like this:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
you can define this view model:
public class ViewModel
{
public ViewModel()
{
Users = new ObservableCollection<User>
{
new User { Id = 1, Name = "John" },
new User { Id = 2, Name = "Mary" },
new User { Id = 3, Name = "Peter" },
new User { Id = 4, Name = "John" },
new User { Id = 5, Name = "John" },
new User { Id = 5, Name = "Peter" }
};
UsersView = new ListCollectionView(Users)
{
Filter = obj =>
{
var user = (User)obj;
return SelectedUser != null && user.Name == SelectedUser.Name && user.Id != selectedUser.Id;
}
};
}
public ObservableCollection<User> Users { get; private set; }
public ICollectionView UsersView { get; set; }
public User SelectedUser
{
get { return selectedUser; }
set
{
if (selectedUser != value)
{
selectedUser = value;
UsersView.Refresh();
}
}
}
private User selectedUser;
}
XAML:
<StackPanel>
<ListView DisplayMemberPath="Name" ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser}"/>
<DataGrid ItemsSource="{Binding UsersView}"/>
</StackPanel>
Result:

wpf datagrid combobox column

I have trouble reading the field. I have tried in different ways but still can not. I want to read the value that the user selected the following 3 values.
Code in XAML
<DataGridComboBoxColumn X:Name="dgcbc" Header="Wynik"/>
Code in C #
List<string> list = new List <string> ();
lista.Add ("Prize");
lista.Add ("Draw");
lista.Add ("Lost");
dgcbc.ItemsSource = list;
This sample might help you in understanding how listbox can be used.
public class Employee
{
public string Name { get; set; }
public string Gender { get; set; }
}
XAML
<StackPanel>
<DataGrid AutoGenerateColumns="False" Name="myGrid" Margin="10">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Name}" />
<DataGridComboBoxColumn Width="100" x:Name="Gender"
SelectedValueBinding="{Binding Gender, Mode=TwoWay}"
DisplayMemberPath="{Binding Gender}" />
</DataGrid.Columns>
</DataGrid>
<Button Name="ShowPersonDetails"
Content="Show Person Details"
Width="200" Height="30"
Click="ShowPersonDetails_Click" Margin="10" />
</StackPanel>
Code-behind
public partial class WPFDataGridComboBox : Window
{
public List<Employee> Employees { get; set; }
public List<string> Genders { get; set; }
public WPFDataGridComboBox()
{
Employees = new List<Employee>()
{
new Employee() { Name = "ABC", Gender = "Female" },
new Employee() { Name = "XYZ" }
};
Genders = new List<string>();
Genders.Add("Male");
Genders.Add("Female");
InitializeComponent();
myGrid.ItemsSource = Employees;
Gender.ItemsSource = Genders;
}
private void ShowPersonDetails_Click(object sender, RoutedEventArgs e)
{
foreach (Employee employee in Employees)
{
string text = string.Empty;
text = "Name : " + employee.Name + Environment.NewLine;
text += "Gender : " + employee.Gender + Environment.NewLine;
MessageBox.Show(text);
}
}
}
I guess you want to enable multi selection in the combobox inside DataGridComboBoxColumn.
Following code project does the same.
http://www.codeproject.com/Articles/21085/CheckBox-ComboBox-Extending-the-ComboBox-Class-and

Bind Object to WPF TreeView

I would like to know how to bind a custom data type to a TreeView.
The data type is basically an arraylist of objects that contain other arraylists. Access would look something like this:
foreach (DeviceGroup dg in system.deviceGroups)
{
foreach (DeviceType dt in dg.deviceTypes)
{
foreach (DeviceInstance di in dt.deviceInstances)
{
}
}
}
I would like the TreeView to look something like this:
DeviceGroup1
--> DeviceType1
--DeviceInstance1
--DeviceInstance2
--> DeviceType2
--DeviceInstance1
DeviceGroup2
--> DeviceType1
--DeviceInstance1
--> DeviceType2
Ok this is where the HierarchicalDataTemplate will save you. The trick is you will need to use two different hierarchical templates, since you have a three-level hierarchy here. I have constructed a simple UserControl to illustrate. First, here is some code-behind creating model data similar to what you have:
public partial class ThreeLevelTreeView : UserControl
{
public ArrayList DeviceGroups { get; private set; }
public ThreeLevelTreeView()
{
DeviceInstance inst1 = new DeviceInstance() { Name = "Instance1" };
DeviceInstance inst2 = new DeviceInstance() { Name = "Instance2" };
DeviceInstance inst3 = new DeviceInstance() { Name = "Instance3" };
DeviceInstance inst4 = new DeviceInstance() { Name = "Instance4" };
DeviceType type1 = new DeviceType() { Name = "Type1", DeviceInstances = new ArrayList() { inst1, inst2 } };
DeviceType type2 = new DeviceType() { Name = "Type2", DeviceInstances = new ArrayList() { inst3 } };
DeviceType type3 = new DeviceType() { Name = "Type3", DeviceInstances = new ArrayList() { inst4 } };
DeviceType type4 = new DeviceType() { Name = "Type4" };
DeviceGroup group1 = new DeviceGroup() { Name = "Group1", DeviceTypes = new ArrayList() { type1, type2 } };
DeviceGroup group2 = new DeviceGroup() { Name = "Group2", DeviceTypes = new ArrayList() { type3, type4 } };
DeviceGroups = new ArrayList() { group1, group2 };
InitializeComponent();
}
}
public class DeviceGroup
{
public string Name { get; set; }
public ArrayList DeviceTypes { get; set; }
}
public class DeviceType
{
public string Name { get; set; }
public ArrayList DeviceInstances { get; set; }
}
public class DeviceInstance
{
public string Name { get; set; }
}
Nothing difficult here, but note that you should use ObservableCollection instead of ArrayList if you want to add and remove from your collections dynamically. Now let's look at the XAML for this control:
<UserControl x:Class="TestWpfApplication.ThreeLevelTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TreeView ItemsSource="{Binding DeviceGroups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceTypes}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceInstances}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And here is the result:
alt text http://img684.imageshack.us/img684/6281/threeleveltreeview.png
I lately had to deal with a similar issue and after much research was able to get to a good generic solution. My problem was a bit more generic: Visualize a .NET object's properties in a tree view.
So given this class
```
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Person> Children { get; set; }
}
I should see a tree like for an example instance:
- root
- FirstName: John
- LastName: Smith
- Children:
- [0]
- FirstName: Ann
- LastName: Smith
```
It's rather difficult to use reflection and go over an object's properties and such. Luckily, we already have a library that does that - Newtonsoft.Json.
My trick is to serialize the object with Newtonsoft.Json, then deserialize with System.Web.Script.Serialization.JavaScriptSerializer.
JavaScriptSerializer returns ArrayLists and Dictionaries, which are pretty simple to go over and add to the tree.
Thanks to this article which gave me some of the concept ideas.
Anyway, here's the entire code:
In XAML:
<TreeView ItemsSource="{Binding TreeItemsSource}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:TreeNode}" ItemsSource="{Binding Path=Children}">
<TreeViewItem>
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal" Margin="-10,0,0,0">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=" : "/>
<TextBox Text="{Binding Path=Value}" IsReadOnly="True"/>
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
In ViewModel:
public IEnumerable<TreeNode> TreeItemsSource
{
get
{
TreeNode tree = TreeNode.CreateTree(SelectedSession);
return new List<TreeNode>() { tree };
}
}
And the TreeNode class
public class TreeNode
{
public string Name { get; set; }
public string Value { get; set; }
public List<TreeNode> Children { get; set; } = new List<TreeNode>();
public static TreeNode CreateTree(object obj)
{
JavaScriptSerializer jss = new JavaScriptSerializer();
var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
Dictionary<string, object> dic = jss.Deserialize<Dictionary<string, object>>(serialized);
var root = new TreeNode();
root.Name = "session";
BuildTree2(dic, root);
return root;
}
private static void BuildTree2(object item, TreeNode node)
{
if (item is KeyValuePair<string, object>)
{
KeyValuePair<string, object> kv = (KeyValuePair<string, object>)item;
TreeNode keyValueNode = new TreeNode();
keyValueNode.Name = kv.Key;
keyValueNode.Value = GetValueAsString(kv.Value);
node.Children.Add(keyValueNode);
BuildTree2(kv.Value, keyValueNode);
}
else if (item is ArrayList)
{
ArrayList list = (ArrayList)item;
int index = 0;
foreach (object value in list)
{
TreeNode arrayItem = new TreeNode();
arrayItem.Name = $"[{index}]";
arrayItem.Value = "";
node.Children.Add(arrayItem);
BuildTree2(value, arrayItem);
index++;
}
}
else if (item is Dictionary<string, object>)
{
Dictionary<string, object> dictionary = (Dictionary<string, object>)item;
foreach (KeyValuePair<string, object> d in dictionary)
{
BuildTree2(d, node);
}
}
}
private static string GetValueAsString(object value)
{
if (value == null)
return "null";
var type = value.GetType();
if (type.IsArray)
{
return "[]";
}
if (value is ArrayList)
{
var arr = value as ArrayList;
return $"[{arr.Count}]";
}
if (type.IsGenericType)
{
return "{}";
}
return value.ToString();
}
}

Categories