I have an object that follows Composite design pattern. I would like to show this object in a WPF using tree view but I have trouble binding the data correctly. I have two classes: Leaf, simple class that doesnt have any children, and Box, compound class that has children elements which could be both of Leaf class of Box class. I also have a common interface called ITree
Interface
public interface ITree
{
string Name { get; }
string Property1 { get; }
string Property2 { get; }
}
Simple class
public class Leaf : ITree
{
string ITree.Name { get { return _name; } }
string ITree.Property1 { get { return property1; } }
string ITree.Property2 { get { return property2; } }
}
Compound class
public class Box : ITree
{
string ITree.Name { get { return _name; } }
string ITree.Property1 { get { return property1; } }
string ITree.Property2 { get { return property2; } }
List<ITree> Children = new List<ITree>();
}
xaml.cs
List<ITree> ListToBind = new List<ITree>();
ITree finalObject = PopulateCompositeObjeectWithData();
ListToBind.Add(finalObject);
xaml
<TreeView ItemsSource="{Binding ElementName=Window, Path= ListToBind}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The tree view I am trying to achieve:
Box - Name
|-Leaf - Name
|-Leaf - Name
|-Box - Name
| |-Leaf - Name
| |-Leaf - Name
Any suggestion or code samples would be greatly appreciated
Thank you
First of all, Children must be public property for you to be able to bind to it:
public class Box : ITree
{
string ITree.Name { get { return _name; } }
string ITree.Property1 { get { return property1; } }
string ITree.Property2 { get { return property2; } }
public List<ITree> Children { get; } = new List<ITree>();
}
Second, you should bind to explicit implemented interface members using parentheses like this:
<TreeView ItemsSource="{Binding ElementName=Window, Path= ListToBind}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding (local:ITree.Name)}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Related
Bit of a newbie question but I can't seem to wrap my head around this one.
My structure is as follows
class Connection
{
private static ObservableCollection<Connection> conlist = getConnections();
private string _channel;
//other fields
public string Channel
{
get
{ return this._channel; }
}
public static ObservableCollection<Connection> Ports
{
get
{ return conlist; }
}
private ObservableCollection<Connection> getConnections(string path)
{
//Gets list of connections from a file
}
class Tab
{
private Connection _channel;
private string _title;
public string Title
{
get
{ return this._title; }
}
public string Connection
{
get { return this._channel; }
}
}
Now I want to have two comboboxes, one to select the Tab which will display the description and I want the other to display all the ports in Connection.Ports but have the selected value initially as the one in the Tab.Connection
<ComboBox x:Name="tab_combobox" ItemsSource="{Binding Tabs}" DisplayMemberPath="Title" Tag="{Binding Channel}" SelectedIndex="0"/>
<ComboBox x:Name="tab_channel_combobox"
ItemsSource="{Binding Source={x:Static model:Connection.Ports}}"
SelectedValue="{Binding SelectedItem.Tag, ElementName=tab_combobox}"
SelectedValuePath="Tag"
/>
Thank you in advance
I am trying to create objects from another object which has a list of objects. But I am getting a casting error on the line where I do the casting. Is there a way to cast object of different collection on to another collection.
Code:
public List<TreeViewModel> getAllTreeNodesFromModel()
{
treeNodeViewModel = treeModel.getTreeNodes().Select(a => new TreeViewModel
{
Children = a.Children.Select(c => new TreeViewModel{Value = c.Value}).ToList(),
Value = a.Value,
}).ToList();
return treeNodeViewModel;
}
// Making list observable
public ObservableCollection<TreeViewModel> TreeView
{
get
{
return treeView;
}
set
{
if (treeView == value) return;
treeView = value;
NotifyPropertyChanged("TreeView");
}
}
XAML:
<TreeView Margin="644,137,6,6" Grid.RowSpan="2" ItemsSource="{Binding TreeView}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MainWindowModel}" ItemsSource="{Binding Children}">
<CheckBox IsChecked="{Binding Value}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Here is my model:
public class TreeModel
{
public string Name { get; set; }
public List<TreeModel> Children { get; set; }
public string Value { get; set; }
public TreeModel()
{
Children = new List<TreeModel>();
}
public TreeModel(string name, List<TreeModel> children)
{
this.Name = name;
this.Children = children;
}
public List<TreeModel> BuildTree(IEnumerable<string> strings)
{
return (
from s in strings
let split = s.Split('.')
group s by s.Split('.')[0] into g // Group by first component (before /)
select new TreeModel
{
Value = g.Key,
Children = BuildTree( // Recursively build children
from s in g
where s.Length > g.Key.Length + 1
select s.Substring(g.Key.Length + 1)) // Select remaining components
}
).ToList();
}
// View Model
public string Value { get; set; }
public List<TreeViewModel> Children { get; set; }
// List VM mapped from Model
public List<TreeViewModel> getAllTreeNodesFromModel()
{
treeNodeViewModel = treeModel.getTreeNodes().Select(a => new TreeViewModel
{
Children = a.Children.Cast<TreeViewModel>().ToList(),
Value = a.Value,
}).ToList();
return treeNodeViewModel;
}
XAML:
<TreeView Margin="644,137,6,6" Grid.RowSpan="2" ItemsSource="{Binding TreeView}" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MainWindowModel}" ItemsSource="{Binding Children}">
<CheckBox IsChecked="{Binding Value}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Copying answer from comment:
You should create TreeViewModel for each model's child, not cast:
Children = a.Children.Select(c => new TreeViewModel{Value = c.Value}).ToList()
As Children are recursive it would better to write it that way:
public List<TreeViewModel> getAllTreeNodesFromModel()
{
treeNodeViewModel = treeModel.getTreeNodes().Select(a => getChildTreeNodesFromModel(a)).ToList();
return treeNodeViewModel;
}
public List<TreeViewModel> getChildTreeNodesFromModel(TreeModel a_model)
{
return new TreeViewModel
{
Value = a_model.Value,
Children = a_model.Children.Select(c => getChildTreeNodesFromModel(c)).ToList()
}
}
I have a sample MVVM WPF application and I'm having problems creating DataTemplates for my dynamically loaded model. Let me try explain:
I have the following simplified classes as part of my Model, which I'm loading dynamically
public class Relationship
{
public string Category { get; set; }
public ParticipantsType Participants { get; set; }
}
public class ParticipantsType
{
public ObservableCollection<ParticipantType> Participant { get; set; }
}
public class ParticipantType
{
}
public class EmployeeParticipant : ParticipantType
{
public EmployeeIdentityType Employee { get; set; }
}
public class DepartmentParticipant : ParticipantType
{
public DepartmentIdentityType Department { get; set; }
}
public class EmployeeIdentityType
{
public string ID { get; set; }
}
public class DepartmentIdentityType
{
public string ID { get; set; }
}
Here is how my View Model looks like. I created a generic object Model property to expose my Model:
public class MainViewModel : ViewModelBase<MainViewModel>
{
public MainViewModel()
{
SetMockModel();
}
private void SetMockModel()
{
Relationship rel = new Relationship();
rel.Category = "213";
EmployeeParticipant emp = new EmployeeParticipant();
emp.Employee = new EmployeeIdentityType();
emp.Employee.ID = "222";
DepartmentParticipant dep = new DepartmentParticipant();
dep.Department = new DepartmentIdentityType();
dep.Department.ID = "444";
rel.Participants = new ParticipantsType() { Participant = new ObservableCollection<ParticipantType>() };
rel.Participants.Participant.Add(emp);
rel.Participants.Participant.Add(dep);
Model = rel;
}
private object _Model;
public object Model
{
get { return _Model; }
set
{
_Model = value;
NotifyPropertyChanged(m => m.Model);
}
}
}
Then I tried creating a ListBox to display specifically the Participants Collection:
<ListBox ItemsSource="{Binding Path=Model.Participants.Participant}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The problem is:
I don't know how to create a template that can handle both type of ParticipantTypes, in this case I could have EmployeeParticipant or DepartmentParticipant so depending on that, the data binding Path would be set to Employee or Department properties accordingly
I though about creating a DataTemplate for each type (e.g. x:Type EmployeeParticipant) but the problem is that my classes in my model are loaded dynamically at runtime so VisualStudio will complain that those types don't exist in the current solution.
How could I represent this data in a ListBox then if my concrete types are not known at compile time, but only at runtime?
EDIT: Added my test ViewModel class
You can still create a DataTemplate for each type but instead of using DataType declarations to have them automatically resolve you can create a DataTemplateSelector with a property for each template (assigned from StaticResource in XAML) that can cast the incoming data item to the base class and check properties or otherwise determine which template to use at runtime. Assign that selector to ListBox.ItemTemplateSelector and you'll get similar behavior to what DataType would give you.
That's not a good view-model. Your view-model should be view-centric, not business-centric. So make a class that can handle all four cases from a visual perspective, then bridge your business classes over to that view-model.
EDIT:
Working off your code:
<ListBox ItemsSource="{Binding Path=Model.Participants}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<TextBlock Text={Binding Id} />
<TextBlock Text={Binding Name} />
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I changed the binding, I assume that was a mistake?
I would create a ViewModel for Participant:
public class Participant_VM : ViewModelBase
{
private string _name = string.Empty;
public string Name
{
get
{
return _name ;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(() => Name);
}
private string _id= string.Empty;
public string Id
{
get
{
return _id;
}
set
{
if (_id== value)
{
return;
}
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Modify the ListBox as follows.
<ListBox ItemsSource="{Binding Model.Participants.Participant}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type loc:DepartmentParticipant}">
<Grid>
<TextBlock Text="{Binding Department.ID}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type loc:EmployeeParticipant}">
<Grid>
<TextBlock Text="{Binding Employee.ID}"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
<ContentPresenter Content="{Binding }"/>
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
loc refers to the namespace in which the DepartmentParticipant and EmployeeParticipant are present. Hope you are familiar with adding namespaces.
Say for example I have the following type:
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
The above type can be assigned to be held in a Propety in a ViewModel like so assuming a corresponding backing field has been created but omitted here ofc:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
// raise property changed etc
}
}
In my xaml a straight forward binding would be:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
Text="{Binding MessageToDisplay,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Can you extend a binding by using the dot notation syntax? e.g:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
**Text="{Binding SelectedSite.Name,**
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Seems like a an interesting feature but my gut instinct is a no as my DC is being assigned at RunTime so at DesignTime or CompileTime, I can't see any clues that could make this feature work or not?
Correct me if have misunderstood what a complex object is, I have simplified mine down for the sake of this question.
Of course this is possible. However, WPF needs to know when any property along the path has changed. To that end, you need to implement INotifyPropertyChanged (or other supported mechanisms). In your example, both Site and the VM containing SelectedSite should implement change notification).
Here's how you could implement the functionality you specified in your question:
// simple DTO
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
// base class for view models
public abstract class ViewModel
{
// see http://kentb.blogspot.co.uk/2009/04/mvvm-infrastructure-viewmodel.html for an example
}
public class SiteViewModel : ViewModel
{
private readonly Site site;
public SiteViewModel(Site site)
{
this.site = site;
}
// this is what your view binds to
public string Name
{
get { return this.site.Name; }
set
{
if (this.site.Name != value)
{
this.site.Name = value;
this.OnPropertyChanged(() => this.Name);
}
}
}
// other properties
}
public class SitesViewModel : ViewModel
{
private readonly ICollection<SiteViewModel> sites;
private SiteViewModel selectedSite;
public SitesViewModel()
{
this.sites = ...;
}
public ICollection<SiteViewModel> Sites
{
get { return this.sites; }
}
public SiteViewModel SelectedSite
{
get { return this.selectedSite; }
set
{
if (this.selectedSite != value)
{
this.selectedSite = value;
this.OnPropertyChanged(() => this.SelectedSite);
}
}
}
}
And your view might look something like this (assuming a DataContext of type SitesViewModel):
<ListBox ItemsSource="{Binding Sites}" SelectedItem="{Binding SelectedSite}"/>
Below is what worked for me:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
RaisePropertyChanged("SelectedSite");
}
}
In my xaml I was able to do:
<TextBox Name="tbSiteName"
Width="250"
Height="30"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsReadOnly="True"
Style="{StaticResource MainTextBoxStyle}"
Text="{Binding SelectedSite.Name,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
This allows you to access data members off the Site Type without having to create individual properties that wrap each data member on the Site Type. Then individual controls can bind to each property declared in the VM. In a one to one fashion, this aproach can become rather verbose. The binding extension attached to the Text property of the TextBox control shown above, shows that we are not binding to a simple straight forward property but actually to a custom type. Potentially removing the need to create more public properties.
I want to make a treeview showing the file system.
public class FileSystem
{
public IList< Folder> folders;
public FileSystem()
{
foreach (DriveInfo di in DriveInfo.GetDrives())
{
Folder f = new Folder(di.Name);
f.fillSubFolders();
folders.Add(f);
}
}
}
public class FileItem
{
public string name;
public FileItem(string _name)
{
name = _name;
}
}
public class Folder
{
public string name;
public IList<Folder> subFolders;
public IList<FileItem> items;
public Folder(string _name)
{
name = _name;
subFolders = new List<Folder>();
items = new List<FileItem>();
}
public void fillSubFolders() {
foreach (string fl in Directory.GetFiles(name))
{
FileItem f = new FileItem(fl);
items.Add(f);
}
foreach (string dir in Directory.GetDirectories(name))
{
Folder f = new Folder(dir);
subFolders.Add(f);
f.fillSubFolders();
}
}
}
What should I add in XAML code in order to bind the data?
<TreeView Height="311" HorizontalAlignment="Left" Name="treeView1" VerticalAlignment="Top" Width="199" ItemsSource="{Binding items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{Binding}">
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
You might want to look at this article (especially the "View implementation" section).
The below link could help
http://www.mattlong.com.au/?p=37