so I have a treeView which works fine. But my problem is that I want to display different trees, of different Types without having to create one for every scenario in XAML. I know how to set the content of a listView from code behind, is the same possible for a treeView? My treeView right now looks like the following but obviously only works for Items of the Type CAN-Message:
<TreeView TreeViewItem.Selected="OnItemSelected" MouseDoubleClick="Tree_MouseDoubleClick" Name="tree">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type srcM:CANMessage}" ItemsSource="{Binding Signals}">
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Name}"/>
<TextBlock Text=" (0x"/>
<TextBlock Text="{Binding CANid}"/>
<TextBlock Text=")"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The thing is I have different scenarios. One for example where I only want to display the Signals List of the class ADTF (shown below) which is of the Type string.
And in another case I want to display the CANMessages list of the CAN Class and for each Message Element I want to set the signal list it contains as subelements (Which is implemented in the XAML example). I have a short version of said classes following:
public class ADTF : ISignalSource
{
public string Id { get; set; }
public List<string> Signals { get; set; }
}
and
public class CAN: ISignalSource
{
public string Id { get; set; }
public List<CANMessage> Signals { get; set; }
}
public class CANMessage
{
public string Id { get; set; }
public List<string> Signals { get; set; }
}
So what I think I need to do is create a HierarchicalDataTemplate for every scenario. But I want to do it in code behind because it seems to take less code than implementing a HierarchicalDataTemplate in XAML for every scenario. Is there a way to o this?
If i understood correctly, You can use Interface to get this working.
Similar question: wpf Treeview with three levels in
Related
At the moment i try, to build something like that with WPF ! Screenshot: http://i.imgur.com/5G6xBTu.png
I have a ObservableCollection with my "Wecker" Objects. I want to dynamicly add items to the listbox with DataBinding that looks like in the Screenshot. Every try failed so far. What do i need to set in the XAML File??
public static ObservableCollection<Wecker> WeckerCollection = new ObservableCollection<Wecker>();
public ObservableCollection<Wecker> MyWeckerCollection
{
get { return WeckerCollection; }
}
Wecker Class
public class Wecker
{
public ArrayList dayOfWeek { get; set; }
public DateTime Alarm { get; set; }
public bool activated { get; set; }
public bool loop { get; set; }
public int maxRunTime { get; set; }
public int id { get; set; }
public bool schlummern { get; set; }
public bool antiStandby { get; set; }
public bool activateMonitor { get; set; }
public string fileName { get; set; }
public string Mp3 { get; set; }
public string Message { get; set; }
public bool ShowMessage { get; set; }
public int volume { get; set; } }
I tryed that last time:
<ListBox HorizontalAlignment="Left" Height="392" VerticalAlignment="Top" Width="431" Margin="15,89,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding MyWeckerCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding activated, Mode=TwoWay}" />
<Label Content="{Binding Alarm}" />
<Label Content="{Binding dayOfWeek}" />
<Label Content="{Binding Message}" />
<Label Content="{Binding Mp3}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I suspect you have not set the DataContext
In the ctor set the DataContext
this.DataContext = this;
or you can do it in XAML in the Window (top) section
DataContext="{Binding RelativeSource={RelativeSource self}}"
If you had set the DataContext then that should work
Are you sure it is in the Windows section
Try (but Path is the default property so that should not be a problem)
ItemsSource="{Binding Path=MyWeckerCollection}"
This may be your problem - public static?
What is the purpose of public static here?
public static ObservableCollection<Wecker> WeckerCollection = new ObservableCollection<Wecker>();
If you want to use a backing property then do it like this
private ObservableCollection<Wecker> myWeckerCollection = new ObservableCollection<Wecker>();
public ObservableCollection<Wecker> MyWeckerCollection
{
get { return myWeckerCollection ; }
}
It sounds like your DataContext is set incorrectly.
You say you are binding the DataContext to {Binding RelativeSource={RelativeSource Self}}, however that just binds the DataContext to the UI object itself.
For example,
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}">
would set the DataContext to the Window object, however the class Window does not have a property called MyWeckerCollection, so your binding would fail.
If you had
<local:MyCustomWindow DataContext="{RelativeSource={RelativeSource Self}}">
and MyCustomWindow has a property called MyWeckerCollection, then it would work.
I also see your comment here which states:
I am not getting any Data at all and i checked the object, "WeckerCollection" it has Data BEFORE setting it as the DataContext
This leads me to believe that either
A) MyWeckerCollection is not a UI control, in which case you need to update your DataContext binding to something other than Self so it correctly binds to your object containing MyWeckerCollection instead of to the UI object.
B) Or this comment can be read as you are setting the DataContext to MyWeckerCollection itself, and of course the class ObservableCollection<Wecker> does not itself have a property called MyWeckerCollection, so the binding would fail.
So the root cause of your problem is the DataContext is not being set correctly.
Unfortunately, the information you provided is not enough for us to help to identify the correct way to set the DataContext, however if you can provide us with more information I'd be glad to help you out.
Often Visual Studio's binding errors and/or Debug mode is enough to point you in the right direction for fixing the DataContext, or there are some 3rd party tools out there like Snoop which I'd highly recommend for debugging binding errors.
Also if you're new to WPF (which it sounds like you are), and are struggling to understand the purpose of the DataContext and how it works, I'd suggest a blog article of mine written for beginners: What is this "DataContext" you speak of?. Its very important that you understand the DataContext if you are going to be working with WPF. :)
Try to add the ItemSource to your ListBox and change the Xaml like this :
Code behind :
this.YourList.ItemsSource = WeckerCollection;
Xaml :
<ListBox HorizontalAlignment="Left" Height="392" VerticalAlignment="Top" Width="431" Margin="15,89,0,0" ScrollViewer.VerticalScrollBarVisibility="Visible">
Xaml code for listview is some thing like this. I populate Id,name and email in each item of the listview.Listview name is resultview.There is a textbox to search.
<TextBox Text="{Binding SearchText, Mode=TwoWay,Source=PropertyChanged}"/>
<ListView Name="ResultsView" IsItemClickEnabled="True" Margin="0,65,4,0" SelectionChanged="ResultsView_SelectionChanged_1" ItemsSource="{Binding contacts}" Background="White" ItemClick="ResultsView_ItemClick" Loaded="ResultsView_Loaded">
<ListView.ItemTemplate>
<TextBlock x:Name="st1" Text="{Binding id}" FontSize="28" Grid.Row="0" Foreground="Black"/>
<TextBlock x:Name="st2" Text="{Binding name}" FontSize="22" Grid.Row="1" Foreground="Black"/>
<TextBlock x:Name="st3" Text="{Binding email}" FontSize="22" Grid.Row="2" Foreground="Black"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is the class
public class Phone
{
public string mobile { get; set; }
public string home { get; set; }
public string office { get; set; }
}
public class Contact
{
public string id { get; set; }
public string name { get; set; }
public string email { get; set; }
public string address { get; set; }
public string gender { get; set; }
public Phone phone { get; set; }
}
public class RootObject
{
public ObservableCollection<Contact> contacts { get; set; }
}
The data context for Resultview is set as followed in my application.
var _Data = JsonConvert.DeserializeObject<RootObject>(jsonString);
ResultsView.DataContext = _Data;
Now the thing i need is when the text in the textbox changes listview items should filter according to the name.
As you told i added this part. bt it shows errors.errors are in comment lines
public string SearchText
{
get
{
return SearchText;
}
set
{
if(SearchText == value)
return;
SearchText = value;
Contacts.Clear();//Here it says contacts doesnt exists in the current context.
foreach(var item in ResultsView.where(contact => contact.Name.Contains(SearchText)))//it shows windows.UI.XAML.Listview does not contaain definion for where
{
Contacts.Add(item);
}
}
}
I am getting those two errors.And i am workin on windows store app not on windows phone.this is all what i have done. if it doesnot work out please provide me an alternate solution.
-Thanks
Here is what you need:
A ViewModel (you should be using Mvvm)
A CollectionViewSource
A method of selecting what to filter by
A method of filtering your data
Create a ViewModel. This is where your Contacts are. Create an ObservableCollection<Contact> Contacts. At initialization, fill this ObservableCollection as you would like. Save off a copy of the base list as _baseContactList;
In your View, create a CollectionViewSource as a StaticResource in your Page.Resources. Bind its contents to your Collection.
Set your ItemsSource of your List to the CVS.
Now, you need to create a method of determining what to sort. You may do something like bind a ComboBox to a string property in your ViewModel. The setter of that property will change the values of your backing ObservableCollection similar to this:
public string SearchText
{
get
{
return _searchText;
}
set
{
if(_searchText == value)
return;
_searchText = value;
Contacts.Clear();
foreach(var item in _baseContactList.Where(contact =>
contact.Name.Contains(_searchText))
{
Contacts.Add(item);
}
}
Now, this is pretty horribly inefficient and may not be idea for you. Firstly, it clears the search after every change. You'll probably want to do something like throttling it with ReactiveExtensions so that it only refills half a second after the last keystroke. Secondly, you'll likely want to replace the condition (contact.Name.Contains(_searchText)) with your custom criteria, possibly including checking the email as well. You may also want to remove case sensitivity (by comparing the ".ToLower()" version of Name and _searchText, or whichever two you're comparing).
So, in order to make this happen, you'll need a TextBox which connects its current string to SearchText. Something like this:
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The Binding statement targets the SearchText property, makes sure that any changes to the TextBox gets reflected in SearchText (via Mode=TwoWay) and then tells it to send the change every time the text changes (at each keystroke) by setting the UpdateSourceTrigger.
Some future work you may want to add would be to have the clearing and refilling be both throttled and asynchronous, along with using an IncrementallyLoadingObservableCollection. Using the incremental loader will actually make it so that you don't necessarily need to throttle as much, as it should only load about 5 at a time. You can look up guides for making one.
That should be it (aside from styling each item). Hope this gets you on your way!
Current Setup
I have a custom class representing an installer file and some properties about that file, conforming to the following interface
public interface IInstallerObject
{
string FileName { get; set; }
string FileExtension { get; set; }
string Path { get; set; }
int Build { get; set; }
ProductType ProductType { get; set; }
Architecture ArchType { get; set; }
bool Configurable { get; set; }
int AverageInstallTime { get; set; }
bool IsSelected { get; set; }
}
My ViewModel has a ReadOnlyObservableCollection<IInstallerObject> property named AvailableInstallerObjects.
My View has a GroupBox containing the ItemsControl which binds to the aforementioned property.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
The binding works correctly, except it's not user friendly. 100+ items are shown.
Need Help Here
I'd like to be able to use my collection of IInstallerObjects but have the View present them with the following ItemTemplate structure.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=ProductType}" Margin="5" />
<ComboBox ItemsSource="{Binding Path=Build}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
Basically I want to be able to group by the ProductType property, showing a list of the available products, with the ComboBox representing the available Build property values for IInstallerObjects of the ProductType.
I can use LINQ in the ViewModel to extract the groupings, but I have no idea how I'd bind to what I've extracted.
My research also turned up the possibility of using a CollectionViewSource but I'm not certain on how I can apply that to my current setup.
I appreciate your help in advance. I'm willing to learn so if I've overlooked something obvious please direct me to the information and I'll gladly educate myself.
If Build should be a collection type.
so your class should be structured like this as an example.
Public Class Customer
Public Property FirstName as string
Public Property LastName as string
Public Property CustomerOrders as observableCollection(OF Orders)
End Class
This should give you the expected results. Each item in the main items presenter will show first name last name and combobox bound to that customers orders.
I know it's simple but this should do.
All you have to do is declare a CollectionViewSource in your view and bind it to the ObservableCollection. Within this object you declare one or more GroupDescriptions which will split up the source into several groups.
Bind this source to the listbox, create a Template for the group description and you are done.
An example can be found here: WPF Sample Series – ListBox Grouping, Sorting, Subtotals and Collapsible Regions. More about CollectionViewSource can be found here: WPF’s CollectionViewSource
The description of your problem lead me to believe you are looking for some kind of colapsing / expanding / grouped / tree-view sort of thing.
XAML for the tree-view
<Window x:Class="WPFLab12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WPFLab12"
Title="MainWindow" Height="350" Width="525">
<Grid>
<GroupBox Header="Products">
<TreeView ItemsSource="{Binding Path=ProductTypes}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type loc:ProductType}"
ItemsSource="{Binding AvailableInstallerObjects}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:InstallerObject}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</GroupBox>
</Grid>
</Window>
What does that do? Well, it establishes a hierarchy of controls in the tree based on the type of data found. The first HierarchicalDataTemplate handles how to display the data for each class, and how they are related in the hierarchy. The second HierarchicalDataTemplate handles how to display each InstallerObject.
Code behind for the Main Window:
public partial class MainWindow : Window
{
public ReadOnlyObservableCollection<ProductType> ProductTypes
{
get { return (ReadOnlyObservableCollection<ProductType>)GetValue(ProductTypesProperty); }
set { SetValue(ProductTypesProperty, value); }
}
// Using a DependencyProperty as the backing store for ProductTypes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProductTypesProperty =
DependencyProperty.Register("ProductTypes", typeof(ReadOnlyObservableCollection<ProductType>), typeof(MainWindow), new UIPropertyMetadata(null));
public MainWindow()
{
this.InitializeComponent();
this.ProductTypes = new ReadOnlyObservableCollection<ProductType>(
new ObservableCollection<ProductType>()
{
new ProductType()
{
Description = "Type A",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "B" },
new InstallerObject() { FileName = "C" },
})
},
new ProductType()
{
Description = "Type B",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "D" },
})
}
});
this.DataContext = this;
}
}
This is totally cheating, though - normally the MainWindow.cs would not serve as the DataContext and have all this stuff. But for this example I just had it make a list of ProductTypes and populate each ProductType class with the InstallerObject instances.
Classes I used, note I made some assumptions and modified your class to suit this View Model better:
public class InstallerObject
{
public string FileName { get; set; }
public string FileExtension { get; set; }
public string Path { get; set; }
public int Build { get; set; }
public bool Configurable { get; set; }
public int AverageInstallTime { get; set; }
public bool IsSelected { get; set; }
}
public class ProductType
{
public string Description { get; set; }
public ReadOnlyObservableCollection<InstallerObject> AvailableInstallerObjects
{
get;
set;
}
public override string ToString()
{
return this.Description;
}
}
So, in MVVM, it seems to me that your current InstallerObject class is more of a Model layer sort of thing. You might consider transforming it in your ViewModel to a set of collection classes that are easier to manage in your View. The idea in the ViewModel is to model things similarly to how they are going to be viewed and interracted with. Transform your flat list of InstallerObjects to a new collection of hierarchical data for easier binding to the View.
More info on various ways to use and customize your TreeView: http://www.codeproject.com/Articles/124644/Basic-Understanding-of-Tree-View-in-WPF
I would like to take a flat list of objects and present them in a TreeView using custom groups.
public enum DocumentType { Current, Inactive, Transition, Checkpack, TechLog, Delivery }
public enum Status { Approved, Rejected, Pending }
public class Document
{
public string Name { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
public DateTime Modified { get; set; }
public string ModifiedBy { get; set; }
public DocumentType Type { get; set; }
public Status Status { get; set; }
}
For example... The user might want to see this list, with the top level group being "Status" and the second level being "Name". This all needs to be configurable from the UI, and I'm struggling to find the best way to achieve it.
I've had a brief look at the CollectionViewSource object, but couldn't find a good way to get it to dynamically build a TreeView.
My gut feeling is that i'll need to do some clever templating in XAML - this is as far as i've got...
<Window.Resources>
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding Name}" />
</DockPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="GroupTemplate"
ItemsSource="{Binding Path=Items}"
ItemTemplate="{StaticResource DocumentTemplate}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Documents.View.Groups}"
ItemTemplate="{StaticResource GroupTemplate}"/>
</Grid>
public CollectionViewSource Documents
{
get
{
var docs = new CollectionViewSource();
docs.Source = DocumentFactory.Documents;
docs.GroupDescriptions.Add(new PropertyGroupDescription("CreatedBy"));
return docs;
}
}
Of course this only displays the Top-level group ("CreatedBy").
After reading a question below, I managed to come up with a better question...
My question: Is it possible to have a generic HierarchicalDataTemplate for a TreeView that displays custom groups applied to a CollectionViewSource.
Honestly this should be marked as a bug in WPF. I too tried and found that Documents.View.Groups throws binding error on View property being null.
Also
<TextBlock Text="{Binding Path=Name}" />
is correct in the GroupTemplate but not in the DocumentTemplate. Note that Groups are of special type GroupItem where Name is one such property that holds the value on which grouping has taken place.
On the other hand in DocumentTemplate, we should refer the property that we need to display on the leaf nodes items e.g. in my example I used Employee.FirstName (I grouped on Gender).
<DataTemplate x:Key="DocumentTemplate">
<DockPanel>
<TextBlock Text="{Binding FirstName}" />
</DockPanel>
</DataTemplate>
Now for binding to take effect I had to introduce a converter which simply returns Groups.
public class GroupsConverter : IValueConverter
{
public object Convert(object value, ...)
{
return ((CollectionViewSource)value).View.Groups;
}
....
}
And tree view binding was changed this way...
<TreeView x:Name="treeView"
ItemsSource="{Binding Path=Documents,
Converter={StaticResource GroupsConverter}}"
ItemTemplate="{StaticResource GroupTemplate}" />
Then this worked for me.
Does this help you?
I have a business object project, which contains composite structure:
public class Tree
{ public IProductComponent TreeRoot { get; set; } }
public interface ITreeComponent
{ public string Name { get; set; } }
public class ContainerComponent : ITreeComponent
{ public BindingList<ITreeComponent> Children { get; set; } }
public class LeafComponent : ITreeComponent
{ }
I need to bind this structure to a TreeView in my WPF project. The tree view first:
<TreeView x:Name="treeView" Grid.ColumnSpan="2">
<TreeView.Resources>
<HierarchicalDataTemplate
ItemsSource="{Binding Children}"
DataType="{x:Type businessObjects:ContainerComponent}">
<Label Content="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type businessObjects:LeafComponent}">
<Label Content="{Binding Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
And the code for binding:
bTreeView = new Binding();
bTreeView.Source = MyTree;
bTreeView.Path = new PropertyPath("TreeRoot.Children");
treeView.SetBinding(TreeView.ItemsSourceProperty, bTreeView);
The problem is that the TReeView does not actually use those templates (it displays only the top level of hierarchy and calls .ToString() to display those items. Please tell me where have I gone wrong. Otherwise, if I set the it is working, but I cannot define two templates there.
Thanks.
Well I notice you are putting the template in resources, not under TreeVeiw.ItemTemplate.
TreeView should have an ItemTemplate (the Hierarchical) and the ItemsSource set. Shouldn't need anything more than that.
Would help with example data for us to test though.
My bad - the Main assembly was loading the dll with entities two times instead of one. That caused it to go crazy - as soon as I fixed it and the assembly loaded once the problems went away.