Vista style ListView (GridViewRowPresenter) - c#

I'd like to display data in ListView with different rows (different style and different content). But as soon as I try to apply a style (which suppose to change nothing) selection stops working and it's not anymore Vista style.
What am I doing wrong? Perhaps wrong approach?
<ListView ItemsSource="{Binding A}">
<ListView.View>
<GridView>
<GridViewColumn Header="B" DisplayMemberBinding="{Binding B}"/>
...
</GridView>
</ListView.View>
<!-- commenting below block will return Vista style back -->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<GridViewRowPresenter Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>

You shouldn't change the ControlTemplate of the ListViewItem, if you want to display data in a ListView with different rows (different style and different content).
Use DataTemplate instead. You can style your rows according to the bound data type.
Assuming you have a view model like this:
public class ViewModel : INotifyPropertyChanged
{
public List<object> A { get; private set; }
public ViewModel()
{
this.A = new List<object> { Brushes.BlueViolet, 42, false };
}
}
Then, just define the DataTemplates for your items that you wish to display in the list view:
<ListView ItemsSource="{Binding A}">
<ListView.View>
<GridView>
<GridViewColumn Header="B"/>
</GridView>
</ListView.View>
<ListView.Resources>
<DataTemplate DataType="{x:Type media:Brush}">
<Rectangle Width="25" Height="25" Fill="{Binding Mode=OneWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Mode=OneWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Int32}">
<TextBlock Text="{Binding Mode=OneWay}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ListView.Resources>
</ListView>
With this approach, you will get the list view rows that represent your data as you wish, retaining the core ListView functionality and "Vista style".

I try to keep it as general as possible so I define a simple enum for different types that each row can take:
public enum RowType { Bool, Int32 }
Now I use it in the view model for row:
public class Row: DependencyObject
{
//create a new row with the given value
//automatically set Value, Info and RowType based on the param
public Row(object val)
{
Value = val;
if (val.GetType() == typeof(bool)) RowType = WpfApplication3.RowType.Bool;
else RowType = WpfApplication3.RowType.Int32;
Info = val.ToString() + " of type " +val.GetType().ToString();
}
public RowType RowType { get; set; }
/// <summary>
/// Gets or sets a bindable value that indicates Value
/// </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(Row), new PropertyMetadata(0));
/// <summary>
/// Gets or sets a bindable value that indicates Info
/// </summary>
public string Info
{
get { return (string)GetValue(InfoProperty); }
set { SetValue(InfoProperty, value); }
}
public static readonly DependencyProperty InfoProperty =
DependencyProperty.Register("Info", typeof(string), typeof(Row), new PropertyMetadata(""));
}
Now that the view model is ready I create a simple TemplateSelector which responds to RowType of the given row:
public class Selector : DataTemplateSelector
{
//Template for RowType==Bool
public DataTemplate Template1 { get; set; }
//Template for RowType==Int32
public DataTemplate Template2 { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var row = item as Row;
if (row == null) return null;
switch (row.RowType)
{
case RowType.Bool:
return Template1;
case RowType.Int32:
return Template2;
default:
return null;
}
}
}
And I use it in Xaml like this:
<Window.Resources>
<!-- selects CellTemplate for column1 (Value) based on RowType -->
<local:Selector x:Key="valueSelector">
<local:Selector.Template1>
<DataTemplate>
<CheckBox IsChecked="{Binding Value, Mode=OneWay}"/>
</DataTemplate>
</local:Selector.Template1>
<local:Selector.Template2>
<DataTemplate>
<TextBlock Text="{Binding Value, Mode=OneWay}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</local:Selector.Template2>
</local:Selector>
<!-- selects CellTemplate for column2 (Info) based on RowType -->
<local:Selector x:Key="infoSelector">
<local:Selector.Template1>
<DataTemplate>
<Canvas Height="16">
<TextBlock Text="{Binding Info, Mode=OneWay}"
Foreground="Blue" VerticalAlignment="Top"/>
</Canvas>
</DataTemplate>
</local:Selector.Template1>
<local:Selector.Template2>
<DataTemplate>
<Canvas Height="16">
<TextBlock Text="{Binding Info, Mode=OneWay}"
VerticalAlignment="Top"/>
</Canvas>
</DataTemplate>
</local:Selector.Template2>
</local:Selector>
</Window.Resources>
<ListView ItemsSource="{Binding A}">
<ListView.View>
<GridView>
<GridViewColumn Header="Value"
CellTemplateSelector="{StaticResource valueSelector}"/>
<GridViewColumn Header="Info" Width="0"
CellTemplateSelector="{StaticResource infoSelector}"/>
</GridView>
</ListView.View>
</ListView>
This is the result:
Not that I wrote this answer based on dymanoid's answer which is accurate based upon given information.

Related

wpf bind to selected listview item and update another list based on that selection

I'm using Visual Studio 2015, I'm trying to teach myself the MVVM pattern, and I'm hitting a road block. My code is loosely based off of Josh Smiths article, I'm using it to help me learn MVVM and create a small app for work in the process.
What I'm trying to accomplish:
I've bound a viewmodel to a listview showing a list of products, each product has a list of "productTemplate" items. In my View I would like this list to populate inside a listbox when a product from my list view is selected. I am implementing INotifyPropertyChanged. I think I'm just missing something simple but I'm not sure.
My code:
Two Models (Product, ProductTemplateItem);
public class Product {
private string _productNum;
private string _productFamily;
public Product() {
}
public string ProductNum { get; set; }
public string ProductFamily { get; set; }
}
public class ProductTemplateItem : ChangeEventHandlerBase {
private string _TemplateItem;
private string _TemplateCode;
public ProductTemplateItem(string templateItem, string templateCode) {
_TemplateItem = templateItem;
_TemplateCode = templateCode;
}
public string TemplateItem {
get { return _TemplateItem; }
set { if(_TemplateItem != value) {
_TemplateItem = value;
OnPropertyChanged("TemplateItem");
}
}
}
public string TemplateCode {
get { return _TemplateCode; }
set {
if (_TemplateCode != value) {
_TemplateCode = value;
OnPropertyChanged("TemplateCode");
}
}
}
public override string DisplayName {
get {
return $"{TemplateItem} - {TemplateCode}";
}
}
}
My ViewModels (Product View Model, brings everything together and adds the product template list, and AlProductsViewModel adds data and exposes everything to be bound in XAML):
public class ProductViewModel : BaseViewModel {
private Product _product;
private bool _isSelected;
private List<ProductTemplateItem> _productTemplate;
public ProductViewModel(string productNum, string productFamily) {
Product.ProductNum = productNum;
Product.ProductFamily = productFamily;
_productTemplate = new List<ProductTemplateItem>();
}
public string ProductNumber {
get { return _product.ProductNum; }
set { if(_product.ProductNum != value) {
_product.ProductNum = value;
OnPropertyChanged("ProductNumber");
}
}
}
public string ProductFamily {
get { return _product.ProductFamily; }
set {
if (_product.ProductFamily != value) {
_product.ProductFamily = value;
OnPropertyChanged("ProductFamily");
}
}
}
public bool IsSelected {
get { return _isSelected; }
set {
if (_isSelected != value) {
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
public List<ProductTemplateItem> ProductTemplate {
get { return _productTemplate; }
set { if (_productTemplate != value) {
_productTemplate = value;
OnPropertyChanged("ProductTemplate");
}
}
}
public Product Product {
get {
if (_product == null) {
_product = new Product();
return _product;
}
else {
return _product;
}
}
set { if(_product != value) {
_product = value;
OnPropertyChanged("Product");
}
}
}
public override string DisplayName {
get {
return Product.ProductNum;
}
}
}
public class AllProductsViewModel : BaseViewModel{
public AllProductsViewModel() {
AddProducts();
}
private void AddProducts() {
List<ProductViewModel> all = new List<ProductViewModel>();
all.Add(new ProductViewModel("4835", "Crop Cart"));
all.Add(new ProductViewModel("780", "Piler"));
all.Add(new ProductViewModel("880", "Piler"));
all.Add(new ProductViewModel("150", "Scooper"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Miscellaneous","MISC"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Drawbar", "DRBR"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Mainframe", "FRAM"));
all[0].ProductTemplate.Add(new Model.ProductTemplateItem("Conveyor", "CONV"));
all[1].ProductTemplate.Add(new Model.ProductTemplateItem("Hello", "HELL"));
AllProducts = new ObservableCollection<ProductViewModel>(all);
}
public ObservableCollection<ProductViewModel> AllProducts { get; private set; }
}
And my XAML code which is a user control with a ListView based off of Josh's code and a listbox that needs to be updated based off of the selection in the ListView:
<UserControl x:Class="Parts_Book_Tool.Views.ProductsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Parts_Book_Tool.Views"
xmlns:viewModel="clr-namespace:Parts_Book_Tool.ViewModel"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewModel:AllProductsViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<CollectionViewSource x:Key="ProductGroups" Source="{Binding Path=AllProducts}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ProductFamily"/>
</CollectionViewSource.GroupDescriptions>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ProductFamily" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<GroupStyle x:Key="ProductGroupStyle">
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
FontWeight="Bold"
Margin="1"
Padding="4,2,0,2"
Text="{Binding Path=Name}"
/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
<Style x:Key="MainHCCStyle" TargetType="{x:Type HeaderedContentControl}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Border
Background="{StaticResource Brush_HeaderBackground}"
BorderBrush="LightGray"
BorderThickness="1"
CornerRadius="5"
Margin="4"
Padding="4"
SnapsToDevicePixels="True"
>
<TextBlock
FontSize="14"
FontWeight="Bold"
Foreground="White"
HorizontalAlignment="Center"
Text="{TemplateBinding Content}"
/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ProductItemsStyle" TargetType="{x:Type ListViewItem}">
<!--
Stretch the content of each cell so that we can
right-align text in the Total Sales column.
-->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<!--
Bind the IsSelected property of a ListViewItem to the
IsSelected property of a CustomerViewModel object.
-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="ItemsControl.AlternationIndex" Value="1" />
<Condition Property="IsSelected" Value="False" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#EEEEEEEE" />
</MultiTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<HeaderedContentControl Header="Model Info" Style="{StaticResource MainHCCStyle}" Grid.Row="0">
<ListView x:Name="lvModelNumbers" Margin="6,2,6,50" DataContext="{StaticResource ProductGroups}"
ItemContainerStyle="{StaticResource ProductItemsStyle}" ItemsSource="{Binding}" >
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="ProductGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Model Number" Width="100" DisplayMemberBinding="{Binding Path=DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
</HeaderedContentControl>
<HeaderedContentControl Header="Model Template" Style="{StaticResource MainHCCStyle}" Grid.Row="1">
<ListBox ItemsSource="{Binding SelectedItem/ProductTemplate, ElementName=lvModelNumbers}" Margin="6,2,6,50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</HeaderedContentControl>
</Grid>
It feels to me like I'm missing the capture of an event to update the listbox, but with my inexperience in MVVM I Can't be sure. I've tried binding to the SelectedItem of the named element but that doesn't work. I can get the listbox to populate if I bind "AllProducts/ProductTemplate", but it just gives me the first indexed values, and doesn't dynamically change when I select another product.
Hopefully that is enough information, and any help would be greatly appreciated. I'm enjoying learning MVVM but it's dificult to wrap my head around.
Thanks,
Try bind the ListBox to the ProductTemplate property of the SelectedItem property of the ListView:
<ListView x:Name="lvModelNumbers" Margin="6,2,6,50"
ItemsSource="{Binding Source={StaticResource ProductGroups}}"
ItemContainerStyle="{StaticResource ProductItemsStyle}">
<ListView.GroupStyle>
<StaticResourceExtension ResourceKey="ProductGroupStyle"/>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Model Number" Width="100" DisplayMemberBinding="{Binding Path=DisplayName}"/>
</GridView>
</ListView.View>
</ListView>
<ListBox ItemsSource="{Binding SelectedItem.ProductTemplate, ElementName=lvModelNumbers}" Margin="6,2,6,50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF DataTemplate binding parameter in Window.Resources

I'm creating a datagrid, with filters in the column headers. It works, but I don't think it's a good approach. Let me show you the code, very simple example:
The View
<Window x:Class="TestDataGridApp.Views.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:viewModels="clr-namespace:TestDataGridApp.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Window.DataContext>
<viewModels:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="DataGridHeader">
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterName, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding ItemCollection}" AutoGenerateColumns="False">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
ViewModel
namespace TestDataGridApp.ViewModels
{
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using TestDataGridApp.Entities;
using Prism.Mvvm;
public class MainWindowViewModel : BindableBase
{
private string _filterId;
private string _filterName;
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public MainWindowViewModel()
{
for (int i = 1; i <= 100; ++i)
{
Items.Add(new Item() {Id = i, Name = $"Item{i}"});
}
}
public string FilterId
{
get { return _filterId; }
set
{
SetProperty(ref _filterId, value);
TriggerFilters();
}
}
public string FilterName
{
get { return _filterName; }
set
{
SetProperty(ref _filterName, value);
TriggerFilters();
}
}
public ObservableCollection<Item> Items
{
get { return _items; }
set { SetProperty(ref _items, value); }
}
public ICollectionView ItemCollection => CollectionViewSource.GetDefaultView(Items);
private void TriggerFilters()
{
ItemCollection.Filter = o => FilterItem((Item)o);
}
private bool FilterItem(Item item)
{
try
{
bool checkId = false;
bool checkName = false;
int itemId = 0;
if (!string.IsNullOrEmpty(FilterId) && int.TryParse(FilterId, out itemId)) checkId = true;
if (!string.IsNullOrEmpty(FilterName)) checkName = true;
if (!checkId && !checkName) return true;
if (item == null) return false;
bool checkIdIsOk = (checkId && item.Id == int.Parse(FilterId) || !checkId);
bool checkNameIsOk = (checkName && item.Name.ToUpper().Contains(FilterName.ToUpper()) || !checkName);
if (checkIdIsOk && checkNameIsOk) return true;
}
catch (Exception e)
{
Console.WriteLine(e);
}
return false;
}
}
}
The Item
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
Basically simple datagrid, 2 columns. In each column there is a TextBox with binded filter. Each filter has its own field, so after the focus is lost, I can filter the grid by all filters.
My issue is.. I have a lot of columns. This is customized datagrid, so you can add and remove columns on the fly and there's a lot of duplicated code. Basically this is duplicated:
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<TextBox DockPanel.Dock="Top"
Text="{Binding DataContext.FilterId, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
</DockPanel>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
... only this <TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterId, ... is changing for different columns.
So, I thought, I can easily replace it with this solution, but now.. I lost binding to my filter fields in the ViewModel:
<Window.Resources>
<DataTemplate x:Key="DataGridHeader">
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterName, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
</DockPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding ItemCollection}" AutoGenerateColumns="False">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
SOO.. I was thinking, to create a Dictionary for filters, where key would be the name of the column and in value I will store current filter (or null, if there's no filter at the moment for this column). Something like..
<TextBox x:Name="Foo" DockPanel.Dock="Top" Text="{Binding DataContext.FiltersDictionary[Foo], RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
But then I have to Biding contexts.. for one TextBox. I'm really not sure about this solution..
My question will be, how to create a parameter for DataTemplate in the above scenario?
Thanks for help!
PS. It's not a duplicate. This question is about "how to create a parameter for DataTemplate". The "duplicated" question is about dictionary as a binding - a potential solution for this question.. although probably NOT. As another user suggested there might be totally different, better solution to solve this problem. Two different things. I'm shocked that I have to explain this
Easiest way is to not rely only on xaml and add some code to help. For example use Loaded event of your TextBox like this:
<DataTemplate x:Key="DataGridHeader">
<DockPanel>
<TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
<TextBox DockPanel.Dock="Top" Loaded="OnFilterBoxLoaded" />
</DockPanel>
</DataTemplate>
And setup binding when it is loaded:
private void OnFilterBoxLoaded(object sender, RoutedEventArgs e) {
var tb = (TextBox)sender;
// find column
DataGridColumnHeader parent = null;
DependencyObject current = tb;
do {
current = VisualTreeHelper.GetParent(current);
parent = current as DataGridColumnHeader;
}
while (parent == null);
// setup binding
var binding = new Binding();
// use parent column header as name of the filter property
binding.Path = new PropertyPath("DataContext.Filter" + parent.Column.Header);
binding.Source = this;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
tb.SetBinding(TextBox.TextProperty, binding);
}
You can use attached property to achieve the same, but I don't think it's needed in this case.
I used Evk solution with DependencyProperty instead of using Header
<controls:FilterDataGridTextColumn FilterName="Name" Header="Name" Binding="{Binding Path=Name}" Width="200" HeaderTemplate="{StaticResource HeaderTemplate}" />
FilterDataGridTextColumn :
public class FilterDataGridTextColumn : DataGridTextColumn
{
public static readonly DependencyProperty FilterNameProperty =
DependencyProperty.Register("FilterName", typeof(string), typeof(FilterDataGridTextColumn));
public string FilterName
{
get { return (string) GetValue(FilterNameProperty); }
set { SetValue(FilterNameProperty, value); }
}
}

A master-detail TabControl binding (1:n) using parent SelectedItem with ICommand

I have XAML related question I have tried to research an answer in vain. I have commented the relevant questions to the XAML. It looks to me this questions is a more complex because of the way I try to arrange things.
Basically I have a main view model used in the TabControl headers and then in the content area I would show a list of items from the main view model. I just don't know how to to the binding. This is the main question. However, I suspect the next and ultimate objectives I have might factor in how to think about this, so I added them too. The rest of the code is for the sake of completeness.
<StackPanel>
<TabControl x:Name="mainsTabControl"
IsSynchronizedWithCurrentItem="True"
IsEnabled="True"
Visibility="Visible"
ItemsSource="{Binding Path=Mains}">
<!-- How to select a different background for the selected header? Note that the background color is "selected tab" if MainContentViewModel.IsActive is not TRUE.
If it is, a different color is chosen. Here this fact is just emulated with IsEnabled property due to well, multi-binding to the rescue (and a converter)? -->
<!-- Is there a clean way to use ICommand binding (RelayCommand) to check if it is OK to change the tab and if necessary, present a dialogue asking for the change? -->
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsActive}"/>
</Style>
</TabControl.ItemContainerStyle>
<!-- This binds to every item in the MainViewModel.Mains collection. Note the question about background color. -->
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<!-- This binding gives reference to the selected MainContentViewModel. -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="10" DataContext="{Binding ElementName=mainsTabControl, Path=SelectedItem, Mode=OneWay}">
<ItemsControl ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
using GalaSoft.MvvmLight;
using System.Collections.ObjectModel;
using System.Linq;
namespace WpfDependencyInjection.ViewModel
{
public class MainContentViewModel: ViewModelBase
{
private ObservableCollection<CustomItemViewModel> customItems;
private mainContentDto MainContent { get; set; }
public string Name { get; }
public bool isActive;
public MainContentViewModel(Engine engine, mainContentDto mainContent)
{
MainContent = mainContent;
Name = MainContent.Name;
IsActive = true;
//The custom items belonging to this main content.
var customItems = engine.CustomItemContents.Where(i => i.MainContentId == MainContent.Id).Select(i => new CustomItemViewModel(engine, i));
CustomItems = new ObservableCollection<CustomItemViewModel>(customItems);
}
public ObservableCollection<CustomItemViewModel> CustomItems
{
get
{
return customItems;
}
set
{
customItems = value;
RaisePropertyChanged(nameof(CustomItems));
}
}
public bool IsActive
{
get
{
return isActive;
}
private set
{
isActive = value;
RaisePropertyChanged(nameof(IsActive));
}
}
}
}
public class CustomItemViewModel: ViewModelBase
{
private Engine Engine { get; }
private ItemTypeDto CustomItem { get; set; }
public string Name { get; }
public CustomItemViewModel(Engine engine, ItemTypeDto customItem)
{
Engine = engine;
CustomItem = customItem;
Name = customItem.Name;
}
}
namespace WpfDependencyInjection
{
public class Engine
{
public string Name { get; } = "EngineMan";
public List<mainContentDto> MainContents { get; set; } = new List<mainContentDto>(new[]
{
new mainContentDto { Name = "Main One", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Version = 1 },
new mainContentDto { Name = "Main Two", Id = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Version = 1 }
});
public List<ItemTypeDto> CustomItemContents { get; set; } = new List<ItemTypeDto>(new ItemTypeDto[]
{
new ItemType1Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType1Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E1"), Name = "ItemType2Dto I", Id = Guid.NewGuid(), Version = 1 },
new ItemType2Dto { MainContentId = Guid.Parse("C51AC758-504B-4914-92DC-5EBE9A1F39E2"), Name = "ItemType2Dto 2", Id = Guid.NewGuid(), Version = 1 }
});
public Engine()
{
}
}
}
<edit: The binding solved partially, though not the ICommand part.
Try this:
<TabControl x:Name="mainsTabControl"
IsEnabled="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Mains}"
SelectedItem="0"
Visibility="Visible">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter ContentSource="Header" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="HotPink" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<Button Background="{x:Null}"
Command="{Binding SomeCommand}"
Content="{Binding Name}"
FocusVisualStyle="{x:Null}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MainContentViewModel}">
<ItemsControl Margin="10"
VerticalAlignment="Top"
ItemsSource="{Binding Path=CustomItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:CustomItem}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Command:
There may be cleaner ways but here we bind IsSelected for the TabItem to the IsSelected property of the viewmodel. This enables having a command that asks if it is ok to navigate and sets IsSelected to true if it is.
Background:
We also retemplate the tabitem so that background works as we want. If you check with Snoop WPF inserts an extra border when the item is selected.
Side note 1:
Don't put the TabControl into a StackPanel like that. A StackPanel sizes to content and will kill scrolling and draw outside the control. Also it comes with a cost, a deep visual tree is not cheap. Same in the ItemTemplate and the other places. In fact StackPanel is rarely right for anything :)
Side note 2:
If you specify DataType in your DataTemplate you get intellisense and some compiletime checking.

Updating a ListBox with different Content On Button Clicks in WPF

So I have a listbox and a tool bar in my WPF app. The tool bar just has regular controls, and the listbox has vertical expanders.
I need the listbox to have a different set of expanders depending on what button is clicked. Right now it looks like such:
<ListBox>
<local:Select_Analysis_Panel/>
</ListBox>
Where local:Select_Analysis_Panel is seperate user control file containing the expanders. What is the best way to go about dynamically updating the ListBox control's content upon a button click?
For the last couple hours I've been trying to use set DataTemplates for each expander set and bind the to the items control property with little avail with the code below. I'm just trying to get basic framework laid out before setting up a MVVM interface. Later on I was going to replace the ItemsSource="Network_anal" with you know ItemsSource="{Binding WhatExpanderViewModelProperty}" or something like that.
<ListBox Width="250" Margin="5,0,0,0">
<ListBox.Resources>
<DataTemplate DataType="Select_Analysis_Panel">
<local:Select_Analysis_Panel/>
</DataTemplate>
<DataTemplate x:Key="Network_anal" DataType="NetworkAnalysis">
<local:NetworkAnalysis/>
</DataTemplate>.Resources>
<ListBox.Template>
<ControlTemplate>
<Border Background="Red"/>
</ControlTemplate>
</ListBox.Template>
<ItemsControl ItemsSource="Network_anal"/>
</ListBox>
Am I taking the right approach to this at all?
Here's what I'm trying to do. Below when the "File" button is clicked the side bar displays these 2 expanders:
And when "Network Design" button these expanders are dipslayed:
Option 1:
Subclassing the sections:
each of these sections could be subclassed from a base section class and a specific DataTemplate could be used for each:
<Window x:Class="MiscSamples.MultiToolbar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="MultiToolbar" Height="300" Width="300">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
</Window.Resources>
<DockPanel>
<ListBox ItemsSource="{Binding Sections}"
SelectedItem="{Binding SelectedSection}"
DisplayMemberPath="Name"
DockPanel.Dock="Top">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}"/>
<Setter Property="MinWidth" Value="80"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border BorderBrush="Black" BorderThickness="1">
<ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}">
<ContentPresenter ContentSource="Content"/>
</ToggleButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
<ScrollViewer Width="300" DockPanel.Dock="Left">
<ContentPresenter Content="{Binding SelectedSection}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:FileSection}">
<TextBlock Text="User Control For File Section"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:NetworkDesignSection}">
<TextBlock Text="User Control For Network Design"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SelectAnalysisSection}">
<TextBlock Text="User Control For Select Analysis"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</ScrollViewer>
<Grid Background="Gray">
<TextBlock Text="Design Surface" TextAlignment="Center" VerticalAlignment="Center" FontWeight="Bold"/>
</Grid>
</DockPanel>
</Window>
Code Behind:
public partial class MultiToolbar : Window
{
public MultiToolbar()
{
InitializeComponent();
var vm = new MainViewModel();
vm.Sections.Add(new FileSection() {Name = "File"});
vm.Sections.Add(new NetworkDesignSection() { Name = "Network Design" });
vm.Sections.Add(new SelectAnalysisSection() { Name = "Select Analysis" });
DataContext = vm;
}
}
Main ViewModel:
public class MainViewModel: PropertyChangedBase
{
private ObservableCollection<Section> _sections;
public ObservableCollection<Section> Sections
{
get { return _sections ?? (_sections = new ObservableCollection<Section>()); }
}
private Section _selectedSection;
public Section SelectedSection
{
get { return _selectedSection; }
set
{
_selectedSection = value;
OnPropertyChanged("SelectedSection");
}
}
}
Sections:
public abstract class Section:PropertyChangedBase
{
public string Name { get; set; }
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
private bool _isVisible = true;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged("IsVisible");
}
}
//Optionally
//public string ImageSource {get;set;}
//ImageSource = "/Resources/MySection.png";
}
public class FileSection: Section
{
///... Custom logic specific to this Section
}
public class NetworkDesignSection:Section
{
///... Custom logic specific to this Section
}
public class SelectAnalysisSection: Section
{
///... Custom logic specific to File Section
}
//...etc etc etc
Result:
Notice that I'm using ToggleButtons bound to the ListBoxItem.IsSelected property to simulate a TabControl-like behavior.
You can set the DataContext of the whole form and bind the ItemsSource of the listbox, or set ItemsSource of the listbox to some collection directly.

MVVM Grouping Items in ListView

I cannot understand what I'm doing wrong. I want to group items in listView.
In result I want to see something like that:
It'm using MVVM pattern. It's my XAML code.
<CollectionViewSource x:Key="EmploeeGroup"
Source="{Binding Path=AllEmploees}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="FirstName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<ListView AlternationCount="2"
DataContext="{StaticResource EmploeeGroup}"
ItemsSource="{Binding IsAsync=True}" Padding="0,0,0,10">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" BorderBrush="#FFA4B97F"
BorderThickness="0,0,0,1">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold"
Text="Name: "/>
<TextBlock FontWeight="Bold"
Text="{Binding Path=FirstName}"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
<ListView.View>
<GridView>
<GridViewColumn Width="150"
Header="FirstName"
DisplayMemberBinding="{Binding Path=FirstName}"/>
<GridViewColumn Width="150"
Header="LastName"
DisplayMemberBinding="{Binding Path=LastName}"/>
</GridView>
</ListView.View>
</ListView>
It's my EmploeeListViewModel.cs
public class EmploeeListViewModel: ViewModelBase
{
readonly EmploeeRepository _emploeeRepository;
private ObservableCollection<EmploeeViewModel> _allmpl;
public ObservableCollection<EmploeeViewModel> AllEmploees
{
get
{
if (_allmpl == null)
{
_allmpl = new ObservableCollection<EmploeeViewModel>();
CreateAllEmploee();
}
return _allmpl;
}
}
public EmploeeListViewModel(EmploeeRepository emploeeRepository)
{
if (emploeeRepository == null)
throw new ArgumentNullException("emploeeRepository");
_emploeeRepository = emploeeRepository;
_emploeeRepository.EmploeeAdded += this.OnEmploeeAddedToRepository;
}
private void CreateAllEmploee()
{
List<EmploeeViewModel> all =
(from emploee in _emploeeRepository.GetEmploees()
select new EmploeeViewModel(emploee)).ToList();
foreach (EmploeeViewModel evm in all)
{
evm.PropertyChanged += this.OnEmploeeViewModelPropertyChanged;
AllEmploees.Add(evm);
}
this.AllEmploees.CollectionChanged += this.OnCollectionChanged;
}
//this.OnCollectionChanged;
//this.OnEmploeeViewModelPropertyChanged;
}
EmploeeViewModel.cs
public class EmploeeViewModel : ViewModelBase
{
#region Fields
Emploee _emploee;
bool _isSelected;
#endregion
#region Constructor
public EmploeeViewModel(Emploee emploee)
{
if (emploee == null)
throw new ArgumentNullException("emploee");
this._emploee = emploee;
}
#endregion
#region Emploee Properties
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value == _isSelected)
return;
_isSelected = value;
base.OnPropertyChanged("IsSelected");
}
}
public string FirstName
{
get { return _emploee.FirstName; }
set
{
if (value == _emploee.FirstName)
return;
_emploee.FirstName = value;
base.OnPropertyChanged("FirstName");
}
}
public string LastName
{
get { return _emploee.LastName; }
set
{
if (value == _emploee.LastName)
return;
_emploee.LastName = value;
base.OnPropertyChanged("LastName");
}
}
#endregion
}
Why can not I bind "FirstName"
property with Expander.Header
TextBlock?
Why have I object type
MS.Internal.Data.CollectionViewGroupInternal
inside Expander.Header(if i wrote inside
Expander.Header
Text="{Binding}")>?
How should I
change my XAML or .CS code to produce
these results?
I found answer on this question by myself.
The object that is sent into the converter is of the type: MS.Internal.Data.CollectionViewGroupInternal.
The main reason is to use "Name" for databinding the group names is simply because that is the property in CollectionViewGroupInternal that contains the name that the current "group collection" has (according to the GroupDescription that you specified).
Not important What was GropertyName in PropertyGroupDescription.
You have to always use {Binding Path=Name} in GroupStyle container.
I had to change only one string in my XAML.
From:
<TextBlock FontWeight="Bold" Text="{Binding Path=FirstName}"/>
To:
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}"/>
Just came across the same Problem regarding the "Name / FirstName" Binding-Problem and found a solution for my project here:
Grouping ListView WPF
In short, withing the Expander-Tag you can set the DataContext to "{Binding Items}". After that you can use your original property names.

Categories

Resources