WPF/XAML using TreeViewItems in a TreeView with Databinding and advanced templates - wpf

I'm pretty new to XAML and to databinding, so hopefully this will be an easy question to someone out there...
I'm trying to build a TreeView using data binding that has a structure similar to this:
Category
Item Name
Item Name
Category
Item Name
So far, so good--I can do that. Next, I want to be able to expand the Item node and show details about that item. This works fine when I build the tree manually in the code-behind, but I want to use data binding instead. I ALMOST have this working, but as it is now, either I can't select the node OR I can't get the node to behave like a TreeViewItem where it shows just the item name when it is collapsed and can expand to show more--in my case a custom defined grid with data on it.
Here's a simplified sample of my XAML code:
<DataTemplate x:Key="ItemDataTemplate">
<TreeViewItem Header="{Binding Path=ItemName}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Price" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Price}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Description" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Qty on Hand" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Qty}"/>
</Grid>
</TreeViewItem>
</DataTemplate>
<HierarchicalDataTemplate x:Key="CategoryDataTemplate" ItemsSource="{Binding Path=Categories}" ItemTemplate="{StaticResource ItemDataTemplate}">
<TextBlock Text="{Binding Path=CategoryName}"/>
</HierarchicalDataTemplate>
Based on this simplified example, the object that was defined in the code-behind would be something like this:
public class Category
{
public ObservableCollection<Item> Items {get; private set;}
public string CategoryName { get; set; }
//constructor and methods here
}
public class Item
{
public string ItemName {get; private set;}
public decimal Price { get; private set; }
public string Description {get; private set; }
public int Qty {get; set;}
//constructor and methods here
}
After reading this question and answer on stackoverflow, I can see that you cannot have your template be a TreeViewItem if you want to select the node. I tried getting rid of the TreeViewItem tag and going straight to the Grid which allows me to select the node, but now I can't collapse the node down to just the ItemName (header text).
I feel like it's darn close... what am I missing?

So, what you want is to make an Item look like it has more child nodes, even though its only "child" is itself, presented with more info?
Well, the most correct way of doing that might be to use an expander in ItemDataTemplate, like so:
<DataTemplate x:Key="ItemDataTemplate">
<Expander Header="{Binding Path=ItemName}" >
<Grid>
<Grid.ColumnDefinitions>
...
<Grid.RowDefinitions>
...
<TextBlock Grid.Row="0" Grid.Column="0" Text="Price" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Price}"/>
<TextBlock ...
</Grid>
</TreeViewItem>
</DataTemplate>
The expander doesn't look exactly like a standard TreeViewItem, but that can be fixed by making your own Template for the Expander.
Now, a slightly more hacky way of doing it would be to have a list of Items in the Item class, where each Item exposes itself as the only list element. Then, ItemDataTemplate would need to be a HierarchicalDataTemplate which referenced another, more detailed, "ItemDetailsTemlate":
public class Item
{
...
public List<Item> InfoWrapper
{
get { return new List<Item>() { this }; }
}
}
Xaml:
<DataTemplate x:Key="ItemDetailsTemplate">
<Grid>
<Grid.ColumnDefinitions>
...
<Grid.RowDefinitions>
...
<TextBlock Grid.Row="0" Grid.Column="0" Text="Price" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Price}"/>
<TextBlock ...
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ItemDataTemplate" ItemsSource="{Binding Path=InfoWrapper}" ItemTemplate="{StaticResource ItemDetailsTemplate}" >
<TextBlock Text="{Binding Path=ItemName}"/>
</HierarchicalDataTemplate>

Related

Sometimes cannot select item in Listbox c#

In my xaml I bind a ObservableCollection<City> Cities to it
and the SelectedItem is SelectedCity
and sometimes when the mouse is over a item, I cannot select it
My ListBox looks like this:
<ListBox ItemsSource="{Binding Model.Cities}" SelectedItem="{Binding Model.SelectedCity}" HorizontalAlignment="Left" Height="468" Margin="10,136,0,0" VerticalAlignment="Top" Width="877">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}" />
<TextBlock Grid.Column="1" Text="{Binding Plz}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and in My Model the code looks like this:
class CitiesModel: MyObservableCollection<City>
{
public ObservableCollection<City> Cities
{
get
{
return _cities;
}
}
private ObservableCollection<City> _cities;
private City _selectedCity;
public City SelectedCity
{
get
{
return _selectedCity;
}
set
{
_selectedCity = value;
RaisePropertyChanged("SelectedCity");
}
}
Can someone explain, why i sometimes cannot select an item?
You mean once you select one, you cannot select a different one.
Your problem is that if you have in the setter.
Remove it.
if(_selectedCity != value)
{
City is an object.
If you compare an object to another then unless you have provided an Equals method, they will be equal if of the same type.
Hence, once you select a City then any other City is equal.

add buttons to table arrangement dynamically in WPF

I would like to make a 2-columns table arrangement and to add buttons to the table at runtime.
what I did is defining nested StackPanels similar to this.
<StackPanel MinWidth="500" MaxWidth="800" MaxHeight="400" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Foreground="Black" Margin="0,0,0,5" FontSize="20">Some Title</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="" MinWidth="100" MinHeight="100" Margin="10,0,0,10"></Button>
<Button Content="" MinWidth="100" MinHeight="100" Margin="10,0,0,10"></Button>
</StackPanel>
</StackPanel>
Is this a correct starting, or there is a better and easier arrangement?
You could use a model-view implementation with ListView and bind it to a collection of items which have the respective handler for the button:
WPF:
<ListView ItemsSource={Binding MyItems}>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding={Binding Name}></GridViewColumn>
<GridViewColumn Header="Button">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Command={Binding ButtonPress}>Click me</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You can also set the ItemsSource inside the .cs file for your view as well, otherwise use a ViewModel class to handle your view and create a ObservableCollection<MyItemWrapper> property which will hold all the table items.
ViewModel:
public class MyViewModel
{
private ObservableCollection<MyItemWrapper> _myItems;
public MyViewModel()
{
_myItems = new ObservableCollection<MyItemWrapper>();
//// add your initial items
}
public ObservableCollection<MyItemWrapper> MyItems
{
get { return _myItems; }
}
}
View:
public partial class MyView : UserControl
{
public MyView(MyViewModel viewModel)
{
DataContext = viewModel;
InitializeComponents()
}
}
MyItem and MyItemWrapper
public class MyItem
{
public string Name { get; set; }
public object Data { get; set; }
}
public class MyItemWrapper
{
private MyItem _item;
public MyItemWrapper(MyItem item)
{
_item = item;
ButtonPress = new DelegateCommand<object>(OnButtonPress);
}
public string Name
{
get { return _item.Name; }
}
public DelegateCommand<object> ButtonPress { get; private set; }
private void OnButtonPress(object args)
{
System.Diagnostics.Debug.WriteLine("Button pressed for: " + Name);
}
}
This will ultimately be able to add/remove items at runtime by using MyItems inside the view model and have your list view always update automatically.
I got it working by using WrapPanel, and I will share what I have did, Thanks for Vlad for binding part:
<ListBox ItemsSource="{Binding CartItemC.CartItems}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="wrapPanel" MaxWidth="300" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel MaxWidth="300">
<Button Command="{Binding ButtonPress}">Click me</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You mentioned you want it dynamically, well you don't want to use StackPanel as you would end up maintaining the width and height of the controls inside it. Given you want the controls to automatically resize its own size.
The best solution is to use Grid if you really want to make a 2 column table arrangement.
Take a look at this example.
Sample code
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>

Design time data for datatemplate in xaml

This may be a stupid question, but is it possible to define some sample data as DataContext in order to see my DataTemplate in DesignView?
At the moment I always have to run my application to see whether my changes are working.
E.g. with the following code DesignView just shows an empty list box:
<ListBox x:Name="standardLayoutListBox" ItemsSource="{Binding myListboxItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Column="0" Content="{Binding text1}" />
<Label Grid.Column="1" Content="{Binding text2}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public class MyMockClass
{
public MyMockClass()
{
MyListBoxItems.Add(new MyDataClass() { text1 = "test text 1", text2 = "test text 2" });
MyListBoxItems.Add(new MyDataClass() { text1 = "test text 3", text2 = "test text 4" });
}
public ObservableCollection<MyDataClass> MyListBoxItems { get; set; }
}
public class MyDataClass
{
public string text1 { get; set; }
public string text2 { get; set; }
}
In Your XAML
Add the namespace declaration
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Add the mock data context to window/control resources
<UserControl.Resources>
<local:MyMockClass x:Key="DesignViewModel"/>
</UserControl.Resources>
Then Modify Your ListBox to Reference the design time object
<ListBox x:Name="standardLayoutListBox"
d:DataContext="{Binding Source={StaticResource DesignViewModel}}"
ItemsSource="{Binding MyListBoxItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Column="0" Content="{Binding text1}" />
<Label Grid.Column="1" Content="{Binding text2}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Wen you are in the designer you should not have to mess with the view models therefore I think it's bet to include design time data in xaml and not in c#, have a look at this simple POCO representation
<ListView ItemsSource="{Binding Items}">
<d:ListView.ItemsSource>
<x:Array Type="{x:Type models:Monkey}">
<models:Monkey Name="Baboon" Location="Africa and Asia"/>
<models:Monkey Name="Capuchin Monkey" Location="Central and South America"/>
<models:Monkey Name="Blue Monkey" Location="Central and East Africa"/>
</x:Array>
</d:ListView.ItemsSource>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Monkey">
<TextCell Text="{Binding Name}"
Detail="{Binding Location}" />
</DataTemplate>
</ListView.ItemTemplate>

WPF Treeview - Binding to a collection with different depths, and styling differently

Apologies for the long post -- Read a few threads about this, but still find it hard to implement. Basically, I have a collection of objects, defined as:
public class LibData
{
public string Name { get; set; }
ObservableCollection<LibObject> _list;
public ObservableCollection<LibObject> List { get { return _list; } }
public LibData(string name, LibDataType type)
{
this.Name = name;
_list = new ObservableCollection<LibObject>();
}
}
and the object:
public class LibObject
{
public string Name { get; set; }
public LibObject(string name)
{
this.Name = name;
}
}
My main problem is in the XAML, and styling this TreeView. I need to have a specific style for a "root" item, and a specific style for a "leaf". Thing is, that one item in the bound list is "Root->Leaf", and another is "Root->Child->Leaf".
I tried this:
<TreeView x:Name="myTree" ItemsSource="{x:Static local:myDataList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=List}" >
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<CheckBox IsChecked="True" Content="HeaderCheckbox"/>
</StackPanel>
</Grid>
<HierarchicalDataTemplate.ItemTemplate >
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True" Content="LeafCheckbox" />
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
This obviously works fine for the "Root->Leaf" item, but not for the "Root-Child-Leaf".
The XAML implementation seems to be more of a "hard coded" solution, where I know that the item layout is always "Root->Leaf" - how do I make this dynamic?
Again, I have seen solutions to different levels (including using converters), but the problem i'm having is that I need specific styles to root and leaf and nothing for levels in between.
I'm wondering if I'm looking at this completely wrong ...
Easy way to do this. Pull the DataTemplates out of the TreeView and put them in the resources section. Specify the DataType property for each DataTemplate, but do not include a key. The ItemTemplateSelector (a property of type DataTemplateSelector) on the TreeView will do the magic of using whichever DataTemplate suits the correct item type.
Create a HierarchicalDataTemplate for types Root and Child, and a DataTemplate for type Leaf.
Something like this:
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:Leaf}">
<Grid>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="True" Content="LeafCheckbox" />
<TextBlock Text="{Binding Path=SomeValue}"/>
</StackPanel>
</Grid>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Child}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Child " />
<TextBlock Text="{Binding Path=SomeValue}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Root}"
ItemsSource="{Binding Children}">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Root " />
<TextBlock Text="{Binding Path=SomeValue}" />
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<TreeView x:Name="myTree" ItemsSource="{Binding}" />
</Grid>

How do I bind list items to an Accordian control from the Silverlight 3 Toolkit?

I have a list of objects in a model. I wish to show elements of the DTO's in the list in my AccordianItem panels. The model is like this:
public class MyModel
{
public List<AnimalDTO> Items { get; set; }
public MyModel()
{
Items = new List<AnimalDTO>
{
new AnimalDTO() {Title = "Monkey", ImageUri = "Images/monkey.jpg"},
new AnimalDTO() {Title = "Cow", ImageUri = "Images/cow.jpg"},
};
}
}
public class AnimalDTO
{
public string Title { get; set; }
public string LongDescription { get; set; }
public string ImageUri { get; set; }
public string NavigateUri { get; set; }
}
I want to show the image in the background image of AccordianItems and lay the LongDescription over a portion of the image.
If I hard code it, I can get the image in the AccordianItem thus...
<layoutToolkit:AccordionItem x:Name="Item2" Header="Item 2" Margin="0,0,10,0" AccordionButtonStyle="{StaticResource AccordionButtonStyle1}" ExpandableContentControlStyle="{StaticResource ExpandableContentControlStyle1}" HeaderTemplate="{StaticResource DataTemplate1}" BorderBrush="{x:Null}" ContentTemplate="{StaticResource CarouselContentTemplate}">
<layoutToolkit:AccordionItem.Background>
<ImageBrush ImageSource="Images/cow.jpg" Stretch="None"/>
</layoutToolkit:AccordionItem.Background>
</layoutToolkit:AccordionItem>
When I try it with a binding like <ImageBrush ImageSource="{Binding Path={StaticResource MyContentTemplate.ImageUri}}" Stretch="None"/> or if I try it with <ImageBrush ImageSource="{Binding Path=Items[0].ImageUri}" Stretch="None"/>
, it throws XamlParseException.
Edit:
I'm able to get some binding of the text over hard-coded images with the following StaticResource (NOTE: I'm hard-coding Items[2], I'm not sure how to index it)
<DataTemplate x:Key="CarouselContentTemplate">
<Grid Width="650" Height="420">
<Grid.RowDefinitions>
<RowDefinition Height="0.476*"/>
<RowDefinition Height="0.524*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
x:Name="Title"
Text="{Binding Items[2].Title}"
Foreground="Black" FontSize="12"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0"
x:Name="LongDescription"
Text="{Binding Items[2].LongDescription}"
TextWrapping="Wrap"FontSize="8"></TextBlock>
</Grid>
</DataTemplate>
Is there a way to index the Items collection in the DataTemplate? Furthermore how do I get the Image to bind rather than hard-coding them in each AccordianItem? Any help in the right direction would be appreciated, most especially how to bind and lay text over an image.
To bind to a collection it must be referenced with ItemsSource="{Binding Items}", where in this case Items is my collection MyModel.Items
<layoutToolkit:Accordion
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ExpandDirection="Right"
Style="{StaticResource AccordionStyle1}"
AccordionButtonStyle="{StaticResource AccordionButtonStyle1}"
MaxHeight="420" MaxWidth="800"
ItemsSource="{Binding Items}" Margin="8,0,-8,-12" Grid.Row="3"
>
<layoutToolkit:Accordion.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"></TextBlock>
</DataTemplate>
</layoutToolkit:Accordion.ItemTemplate>
Note that a collection should be bound with ItemsSource, which is plural as a mnemonic. and individual members of elements are bound within control of <layoutToolkit:Accordian.ItemTemplate> Here I am showing MyCollection.Title in a TextBlock control. I shall update this with full code or a link to my blog for a full example later.

Resources