I have a ListView that contains two types of objects, single and multiple.
The single is a ordinary TextBlock while the multiple is a ComboBox with items.
I'm trying to group the items in the ComboBox without success. Is it possible? Or should I go for a different approach?
What I'm trying to achieve:
[ComboBox v]
[Header ]
[ Item]
[ Item]
[Header ]
[ Item]
It is possible. Use a ListCollectionView with a GroupDescription as the ItemsSource and just provide a GroupStyle to your ComboBox. See sample below:
XAML:
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
xmlns:uc="clr-namespace:StackOverflow.UserControls"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ComboBox x:Name="comboBox">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
Code-behind:
namespace StackOverflow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//this.comboBox.DataContext = this;
List<Item> items = new List<Item>();
items.Add(new Item() { Name = "Item1", Category = "A" });
items.Add(new Item() { Name = "Item2", Category = "A" });
items.Add(new Item() { Name = "Item3", Category = "A" });
items.Add(new Item() { Name = "Item4", Category = "B" });
items.Add(new Item() { Name = "Item5", Category = "B" });
ListCollectionView lcv = new ListCollectionView(items);
lcv.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
this.comboBox.ItemsSource = lcv;
}
}
public class Item
{
public string Name { get; set; }
public string Category { get; set; }
}
}
Related
I am trying to display collection of objects using ItemsControl. This is the simple example I created;
<Window x:Class="PhotoWieverWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PhotoWieverWPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ScrollViewer>
<ItemsControl x:Name="Images" ItemsSource="{Binding Path=Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=FullName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
using System.Collections.ObjectModel;
using System.Windows;
namespace PhotoWieverWPF
{
/// <summary>
/// MainWindow.xaml etkileşim mantığı
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<ListItem> Items { get; } = new ObservableCollection<ListItem>();
public MainWindow()
{
InitializeComponent();
Items.Add(new ListItem("Item 1"));
Items.Add(new ListItem("Item 2"));
Items.Add(new ListItem("Item 3"));
Items.Add(new ListItem("Item 4"));
Items.Add(new ListItem("Item 5"));
Items.Add(new ListItem("Item 6"));
}
public class ListItem
{
public string FullName { get; }
public ListItem(string fullname)
{
FullName = fullname;
}
}
}
}
However, I am getting a blank window with no errors. I suppose I made a mistake with binding, but I can't figure out what it is.
Like #Clemens said you forget to set DataContext for your View. You can easily set it to your code-behind's constructor like this:
public MainWindow()
{
InitializeComponent();
DataContext = this; // you need this
Items.Add(new ListItem("Item 1"));
Items.Add(new ListItem("Item 2"));
Items.Add(new ListItem("Item 3"));
Items.Add(new ListItem("Item 4"));
Items.Add(new ListItem("Item 5"));
Items.Add(new ListItem("Item 6"));
}
I am an experienced user of Winforms and I am now playing with WPF, but I am struggling a bit.
I have a datagrid with 2 columns.
The first column is a DataGridTextColumn which is bound to the "Name" property of my object.
The second column is bound to the "Power" property of my object.
When the user edits the Power column, I would like to display a combo box that lists all the Name of the first column in addition of "None" as first item.
How can I do that?
In addition, if the user updates any Name of the first column, I would like the change to be reflected in the Power column. Is it possible?
In code behind:
public partial class MainWindow : Window
{
ObservableCollection<MyObject> objects = new ObservableCollection<MyObject>();
public MainWindow()
{
InitializeComponent();
dgObjects.ItemsSource = objects;
}
}
public class MyObject
{
public String Name { get; set; }
public String Power { get; set; }
}
In Xaml:
<DataGrid Name="dgObjects" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridComboBoxColumn Header="Power" Binding="????"/>
</DataGrid.Columns>
</DataGrid>
Thanks
You can do this via binding on the DataContext. Though, first you need to set up the Window's DataContext properly. ItemsSource should not be done via code-behind (this is WPF, use binding!).
Set up your class structure like:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ObservableCollection<MyObject> Objects { get; } = new ObservableCollection<MyObject>();
public ViewModel()
{
Objects.Add(new MyObject
{
Name = "Name"
});
Objects.Add(new MyObject
{
Name = "Name2"
});
Objects.Add(new MyObject
{
Name = "Name3"
});
}
}
public class MyObject
{
public String Name { get; set; }
public String Power { get; set; }
}
Now, update your xaml. You will need to use a CellTemplate and a standard Combobox. The ComboBox will then bind to the Objects collection, display the Name, but set the value in Power.
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
x:Name="MyWindow">
<Grid>
<DataGrid Name="dgObjects" AutoGenerateColumns="False"
ItemsSource="{Binding Objects}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ElementName=MyWindow, Path=DataContext.Objects}"
DisplayMemberPath="Name"
SelectedItem="{Binding Power}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Given the following classes (where ViewModelBase contains the INotifyPropertyChanged members)
namespace WpfApplication1
{
class ViewModel : ViewModelBase
{
private Person selectedPerson;
public ObservableCollection<Person> Persons { get; set; }
public Person SelectedPerson
{
get
{
return selectedPerson;
}
set
{
selectedPerson = value;
OnPropertyChanged("SelectedPerson");
}
}
public ViewModel()
{
Persons = new ObservableCollection<Person>();
Persons.Add(new Person { FirstName = "John", LastName = "Smith" });
Persons.Add(new Person { FirstName = "Jane", LastName = "Jones" });
SelectedPerson = new Person();
}
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
And the following XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:ViewModel />
</Window.DataContext>
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding Persons}"
DisplayMemberPath="FirstName"
SelectedItem="{Binding SelectedPerson}"/>
<StackPanel DataContext="{Binding SelectedPerson}">
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
If the value in the TextBox bound to FirstName is changed, the change is automatically reflected in the ListBox. Assuming that I add a Button control and associated Command, is there a way instead to have the changed value propagated to the ListBox only if that Button has been pressed?
I have a CellTemplate for a column in a ListView. The CellTemplate contains a ComboBox which has an ItemTemplate. Both ItemsSource and SelectedItem is bound to another ViewModel.
The ListView is bound to an ObservableCollection on a ViewModel. Above the ListView there is a toolbar with the buttons to move the selected item up and down. I buttons a bound to and ICommand which will make a Move on the ObservableCollection.
The view is updated fine, but the selected item in the ComboBox is not using the DataTemplate and is just showing the type name.
I found out that everything is working fine if IsEditable = false, but I need this to be true.
I have created a small project that verifies the problem. Perhaps this is an issue in WPF.
Here is XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication3="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type WpfApplication3:Item}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate x:Key="cellTemplate">
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" Width="100" IsEditable="true" TextSearch.TextPath="Name"/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<ToolBar>
<Button Content="Add" Command="{Binding AddItemCommand}"/>
<Button Content="Up" Command="{Binding MoveItemUpCommand}" CommandParameter="{Binding ElementName=listView, Path=SelectedItem}"/>
<Button Content="Down" Command="{Binding MoveItemDownCommand}" CommandParameter="{Binding ElementName=listView, Path=SelectedItem}"/>
</ToolBar>
<ListView x:Name="listView" ItemsSource="{Binding Collection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" CellTemplate="{StaticResource cellTemplate}"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ICommand AddItemCommand { get; private set; }
public ICommand MoveItemUpCommand { get; private set; }
public ICommand MoveItemDownCommand { get; private set; }
public ObservableCollection<Row> Collection { get; set; }
public ViewModel()
{
Collection = new ObservableCollection<Row>();
AddItemCommand = new RelayCommand(AddItem);
MoveItemUpCommand = new RelayCommand<Row>(MoveItemUp, CanMoveItemUp);
MoveItemDownCommand = new RelayCommand<Row>(MoveItemDown, CanMoveItemDown);
}
private bool CanMoveItemDown(Row arg)
{
if (arg == null)
return false;
return Collection.Last() != arg;
}
private void MoveItemDown(Row obj)
{
var index = Collection.IndexOf(obj);
Collection.Move(index, index + 1);
}
private bool CanMoveItemUp(Row arg)
{
if (arg == null)
return false;
return Collection.First() != arg;
}
private void MoveItemUp(Row row)
{
var index = Collection.IndexOf(row);
Collection.Move(index, index - 1);
}
private void AddItem()
{
Collection.Add(new Row());
}
}
public class Row
{
public Row()
{
Items = new List<Item> { new Item { Name = "Test1" }, new Item { Name = "Test2" } };
}
public List<Item> Items { get; set; }
public Item SelectedItem { get; set; }
}
public class Item
{
public string Name { get; set; }
public int Order { get; set; }
}
I am sure I am missing something simple/obvious, but I cannot seem to bind the data of a ListView within a ListView
<Window x:Class="TestList.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">
<Window.Resources>
<DataTemplate x:Key="InsideListTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="test" Width="50"></TextBlock>
<TextBlock Text="{Binding OrderId}" Width="50"></TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OrdersTemplate">
<ListView HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
MinWidth="100"
MinHeight="25"
ItemsSource="{Binding Orders}"
ItemTemplate="{StaticResource InsideListTemplate}"
>
</ListView>
</DataTemplate>
<DataTemplate x:Key="CustomersTemplate">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding CustomerId}" Width="50" Foreground="Navy" VerticalAlignment="Center" />
<ListBox ItemsSource="{Binding Orders}" ItemTemplate="{StaticResource OrdersTemplate}" HorizontalContentAlignment="Stretch"></ListBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<ListView Name="listView" ItemTemplate="{StaticResource CustomersTemplate}" >
</ListView>
</DockPanel>
using System.Collections.Generic;
namespace TestList
{
public partial class MainWindow
{
public class Customer
{
public int CustomerId { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
var customers = new List<Customer>
{
new Customer
{
CustomerId = 1,
Orders = new List<Order>
{
new Order {OrderId = 1},
new Order {OrderId = 2}
}
},
new Customer
{
CustomerId = 2,
Orders = new List<Order>
{
new Order {OrderId = 1},
new Order {OrderId = 2}
}
}
};
listView.ItemsSource = customers;
}
}
}
This is an explanation of Hadis answer:
You are binding a ListBox to the Orders collection within the customer template. And then in the orders template you define a ListView binding again to the orders. This means that the binding path at that point is customer.orders.orders which does not exists.
If you just remove the OrdersTemplate and place the ListView where the ListBox is in the customer template then it works.
How about changing it list this:
public partial class MainWindow : Window
{
public class Customer
{
public int CustomerId { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public List<OrderItem> Items { get; set; }
}
public class OrderItem
{
public int No { get; set; }
public string Name { get; set; }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
var customers = new List<Customer>
{
new Customer
{
CustomerId = 1,
Orders = new List<Order>
{
new Order {OrderId = 1, Items = new List<OrderItem>(new[] { new OrderItem { Name = "CD Player", No = 1}, new OrderItem { Name = "VCR Player", No = 2} })},
new Order {OrderId = 2, Items = new List<OrderItem>(new[] { new OrderItem { Name = "DVD Player", No = 1} })}
}
},
new Customer
{
CustomerId = 2,
Orders = new List<Order>
{
new Order {OrderId = 1},
new Order {OrderId = 2}
}
}
};
listView.ItemsSource = customers;
}
}
and on your Xaml modify it like this:
<DataTemplate x:Key="InsideListTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding No}" Width="50"></TextBlock>
<TextBlock Text="{Binding Name}" Width="50"></TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OrdersTemplate">
<StackPanel>
<TextBlock Text="{Binding OrderId}" />
<ListView HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
MinWidth="100"
MinHeight="25"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource InsideListTemplate}" />
</StackPanel>
</DataTemplate>
And your output will display the details