WPF DataTemplate binding parameter in Window.Resources - c#

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); }
}
}

Related

Can you bind a complex type in a DataGridComboBoxColumn in a DataGrid in WPF?

So this one I am curious on as I may have to change my code base if I cannot get the data right. I was hoping a binding expert on WPF has had something similar and knew how to do it. I was following this guide, http://wpfthoughts.blogspot.com/2015/04/cannot-find-governing-frameworkelement.html, for binding a value in a list that is shown in datagrid to a combobox. Works great, if your property in the collection of objects is a primitive type. If it is complex not so much. I also want it to update the property when it changes implementing INotifyPropertyChanged.
Feel free to download the source code for easier reference: https://github.com/djangojazz/ComboBoxInDataGridViewWPF
BaseViewModel(just for INotifyPropertyChanged reuse):
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Essentially I have models as such:
public class Type
{
public Type(int typeId, string typeName)
{
TypeId = typeId;
TypeName = typeName;
}
public int TypeId { get; set; }
public string TypeName { get; set; }
}
public class TransactionSimple : BaseViewModel
{
public TransactionSimple(int transactionId, string description, int typeId, decimal amount)
{
TransactionId = transactionId;
Description = description;
TypeId = typeId;
Amount = amount;
}
public int TransactionId { get; set; }
public string Description { get; set; }
private int _typeId;
public int TypeId
{
get { return _typeId; }
set
{
_typeId = value;
OnPropertyChanged(nameof(TypeId));
}
}
public decimal Amount { get; set; }
}
public class TransactionComplex : BaseViewModel
{
public TransactionComplex(int transactionId, string description, int typeId, string typeName, decimal amount)
{
TransactionId = transactionId;
Description = description;
Type = new Type(typeId, typeName);
Amount = amount;
}
public int TransactionId { get; set; }
public string Description { get; set; }
private Type _type;
public Type Type
{
get { return _type; }
set
{
if(_type != null) { MessageBox.Show($"Change to {value.TypeName}"); }
_type = value;
OnPropertyChanged(nameof(Type));
}
}
public decimal Amount { get; set; }
}
And the ViewModel:
public sealed class MainWindowViewModel : BaseViewModel
{
private ObservableCollection<TransactionSimple> _simples;
private ObservableCollection<TransactionComplex> _complexes;
public MainWindowViewModel()
{
FakeRepo();
}
private ReadOnlyCollection<Type> _types;
public ReadOnlyCollection<Type> Types
{
get => (_types != null) ? _types : _types = new ReadOnlyCollection<Type>(new List<Type> { new Type(1, "Credit"), new Type(2, "Debit") });
}
public ObservableCollection<TransactionSimple> Simples
{
get { return _simples; }
set
{
_simples = value;
OnPropertyChanged(nameof(Simples));
}
}
public ObservableCollection<TransactionComplex> Complexes
{
get { return _complexes; }
set
{
_complexes = value;
OnPropertyChanged(nameof(Complexes));
}
}
private void FakeRepo()
{
var data = new List<TransactionComplex>
{
new TransactionComplex(1, "Got some money", 1, "Credit", 1000m),
new TransactionComplex(2, "spent some money", 2, "Debit", 100m),
new TransactionComplex(3, "spent some more money", 2, "Debit", 300m)
};
Complexes = new ObservableCollection<TransactionComplex>(data);
Simples = new ObservableCollection<TransactionSimple>(data.Select(x => new TransactionSimple(x.TransactionId, x.Description, x.Type.TypeId, x.Amount)));
}
}
UPDATED 2:24 PM PST USA: And finally the view(almost working):
<Window x:Class="ComboBoxInDataGridViewWPF.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:ComboBoxInDataGridViewWPF"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="Types" Source="{Binding Types}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="SimpleExample" />
<DataGrid Grid.Row="1" ItemsSource="{Binding Simples}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="TransactionId" Binding="{Binding TransactionId}" />
<DataGridTextColumn Header="Description" Binding="{Binding Description}" />
<DataGridComboBoxColumn Header="Type" ItemsSource="{Binding Source={StaticResource Types}}" DisplayMemberPath="TypeName" SelectedValuePath="TypeId" SelectedValueBinding="{Binding Path=TypeId}" />
<DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
</DataGrid.Columns>
</DataGrid>
<Border Grid.Row="2" Height="50" Background="Black" />
<Label Content="ComplexObjectExample" Grid.Row="3" />
<DataGrid Grid.Row="4" ItemsSource="{Binding Complexes}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="TransactionId" Binding="{Binding TransactionId}" />
<DataGridTextColumn Header="Description" Binding="{Binding Description}" />
<!--This one works for the displays but not for the updates
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource Types}}" DisplayMemberPath="TypeName" SelectedItem="{Binding Type, Mode=TwoWay}" SelectedValue="{Binding Type.TypeId}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type.TypeName}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<!--This one works but the initial displays are wrong. This seems to be the closest to what I want-->
<DataGridComboBoxColumn Header="Type" SelectedItemBinding="{Binding Type}" >
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Types}"/>
<Setter Property="DisplayMemberPath" Value="TypeName" />
<Setter Property="SelectedItem" Value="{Binding Type}" />
<Setter Property="IsReadOnly" Value="True"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Types}"/>
<Setter Property="DisplayMemberPath" Value="TypeName" />
<Setter Property="SelectedItem" Value="{Binding Type}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<!--This one does not work at all
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName" SelectedItem="{Binding Type}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>-->
<DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
The problem is shown like this:
I can obviously get the items bound to the ComboBox and I have seen by adding Observable Collections(not shown) and raising properties that the complex type is getting called. But it will not display no matter what I try. Trying the property of the property like Type.TypeName or such with different combinations doesn't work. Any ideas?
This ridiculous behaviour is well known. Because DataGridColumn lies not in the visual tree, the classic way using the DataGridComboBoxColumn to bind the items from parent like you tried is not working.
Instead you could create DataGridTemplateColumn with a ComboBox inside. This should solve your problem nearly in the same way. If you want to bind the TypeId this code works:
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName"
SelectedValuePath="TypeId"
SelectedValue="{Binding Path=TypeId, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Binding the whole Type could be done by changing the ComboBox to:
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
DisplayMemberPath="TypeName"
SelectedItem="{Binding Path=Type, UpdateSourceTrigger=PropertyChanged}"/>
Alternatively you can have a look at this question where other possible solutions are described.

Datagrid inserted row without leaving the row is always empty

I was wondering if it is possible or its just me hoping for something not realizable.
I'm explaining my problem:
I have a Datagrid as simple as it could be:
<DataGrid Background="Transparent" RowBackground="MidnightBlue"
HorizontalGridLinesBrush="White" VerticalGridLinesBrush="White"
CanUserReorderColumns="False" AutoGenerateColumns="False"
SelectionMode="Single" CanUserSortColumns="False"
ItemsSource="{Binding Patrols}"
SelectedItem="{Binding SelectedPatrol}">
<DataGrid.Columns>
<DataGridTextColumn CellStyle="{DynamicResource DataGridCell}"
EditingElementStyle="{DynamicResource DataGridTextBox}"
Binding="{Binding Phone}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="ExtraBold" FontSize="20"
Text="Gsm"/>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Height="30" Width="30"
Visibility="{Binding Path=DataContext, Converter={StaticResource IsNamedObjectVisibilityConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridRow}}"
Command="{Binding Path=DataContext.InsertOrUpdateCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"
ToolTip="Enregistrer">
<Image Source="../../Images/Save.png"/>
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</Grid>
There are my properties in viewModel:
private ObservableCollection<Patrol> _patrols;
public ObservableCollection<Patrol> Patrols
{
get { return _patrols; }
set { Set(() => Patrols, ref _patrols, value); }
}
private Patrol _selectedPatrol;
public Patrol SelectedPatrol
{
get { return _selectedPatrol; }
set { Set(() => SelectedPatrol, ref _selectedPatrol, value); }
}
private ICommand _insertOrUpdateCommand;
public ICommand InsertOrUpdateCommand
{
get { return _insertOrUpdateCommand = _insertOrUpdateCommand ?? new RelayCommand(InsertOrUpdatePatrol); }
}
Then my function where i'm supposed to retreive my new object from selectedItem property:
private void InsertOrUpdatePatrol()
{
var patrolProcessor = new PatrolProcessor();
patrolProcessor.InsertOrUpdatePatrol(SelectedPatrol);
RefreshPatrolList();
}
What i would like to achieve here is to add a row and retrieve it directly in my viewmodel in my binded property SelectedPatrol without leaving the selection of my row.
Currently when I'm clicking on my row button i just have an empty Object in my viewmodel side. But if i'm leaving the current selection then pushing the button in my new row freshly added, then my object is fill.
Am i missing something?
Thanks in advance for your answers.
Cya guyz.

Loading different usercontrols on selection of checkbox in mvvm pattern

I am working on wpf mvvm pattern.
I have designed multiple UserControls for different checkboxes. On selection of a checkbox, the corresponding UserControl should get loaded in the right side.
For a single checkbox, I have added the UserControl in MainView.xaml as this:
<StackPanel>
<UserControl Name="CCS01" ScrollViewer.CanContentScroll="True" Margin="5" >
<local:CCS01 HorizontalAlignment="Left"></local:CCS01>
</UserControl>
</StackPanel>
I have stored the list of Checkboxes in a datagrid in a different UserControl like this
<DataGrid Width="150" Grid.Row="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" Name="dataGridCustomers" ItemsSource="{Binding Path=UsecaseListItems}" CanUserResizeColumns="False" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="40" Header="Select" Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}">
</DataGridCheckBoxColumn>
<DataGridTextColumn Width="85" Binding="{Binding Path=UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
The viewmodel class is like this:
[![private string _usecaseName;
public string UsecaseName
{
get { return _usecaseName; }
set
{
_usecaseName = value != null ? value.Trim() : null;
OnPropertyChanged("UsecaseName");
}
}
private string _description;
public string Description
{
get { return _description; }
set
{
_description = value != null ? value.Trim() : null;
OnPropertyChanged("Description");
}
}
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
private bool _AllSelected;
public bool AllSelected
{
get { return _AllSelected; }
set
{
_AllSelected = value;
foreach (var reportListItemModel in UsecaseListItems)
{
reportListItemModel.IsSelected = this._AllSelected;
}
OnPropertyChanged("AllSelected");
}
}
private ObservableCollection<UseCase> _usecaseListItems = new ObservableCollection<UseCase>();
public ObservableCollection<UseCase> UsecaseListItems
{
get { return _usecaseListItems; }
set {
_usecaseListItems = value;
OnPropertyChanged("UsecaseListItems");
}
}][1]][1]
In short, I want to divide the page into two columns, on the left I have a list of UserControls and right I want to view the selected UserControl (only one can be selected) and how to bind the selected checkbox with the respective UserControl in ViewModel class.
For reference, I am adding the image of UI here:
You try Something like this :
*.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" ItemsSource="{Binding Path=UsecaseListItems}"
Name="dataGridCustomers" CanUserResizeColumns="False" CanUserResizeRows="False"
SelectionChanged="dataGridCustomers_SelectionChanged">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="auto" Header="Select" Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}">
</DataGridCheckBoxColumn>
<DataGridTextColumn Width="auto" Binding="{Binding UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Border Grid.Column="1" BorderThickness="1" BorderBrush="Gray">
<Grid Grid.Column="1" x:Name="HostGrid" Margin="5">
</Grid>
</Border>
</Grid>
*.cs (code behind)
private void dataGridCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var SelectedItem = dataGridCustomers.SelectedItem;
string UserControlName = ((UseCase)SelectedItem).UsecaseName;
Assembly ass = Assembly.GetExecutingAssembly();
foreach (var item in ass.GetTypes())
{
if (item.Name == UserControlName)
{
UserControl uc = (UserControl)Activator.CreateInstance(item,null);
HostGrid.Children.Add(uc);
}
}
}
If you can remove the checkbox, it would be better. Because the CheckBox allows multi-selection
Create a class MyControl with 2 properties string Name (will be binded to your datagrid) and UserControl Control it will be your control.
Create a list of your controls.
In your ViewModel create a MyControl SelectedControl property, this will tell you what's the selected controls, and bind it to the datagrid
In your View add a ContentControl and bind it to: SelectedControl.Control

wpf DataGridTemplateColumn CellEditingTemplate - combobox's itemssource issue

I have TabControl with two items.
<Window x:Class="WpfApplication1.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">
<Grid>
<TabControl>
<TabItem Header="Tab1" Content="{Binding}" ContentTemplate="{StaticResource Tab1}"/>
<TabItem Header="Tab2" ContentTemplate="{StaticResource Tab2}"/>
</TabControl>
</Grid>
</Window>
There are DataGrid control in one tabItem of TabControl. DataTemplates of TabItems:
<DataTemplate x:Key="Tab1">
<DataGrid ItemsSource="{Binding Entities}" x:Name="dataGridEx">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="100"/>
<DataGridTemplateColumn Header="Position" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Position}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Position, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=DataContext.Positions, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
<DataTemplate x:Key="Tab2">
<Grid>
<TextBox Text="Empty tab"></TextBox>
</Grid>
</DataTemplate>
DataContext of MainWindow:
public class MainWindowViewModel
{
public ObservableCollection<Entity> Entities { get; set; }
public List<string> Positions { get { return new List<string>() { "Manager", "Seller" }; } }
public MainWindowViewModel()
{
Entities = new ObservableCollection<Entity>()
{
new Entity() {Name = "John", Position = "Manager"},
new Entity() {Name = "Mark", Position = "Seller"},
new Entity() {Name = "Alice"}
};
}
}
The Entity class:
public class Entity : INotifyPropertyChanged
{
private string _name;
private string _position;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged("Position");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
#endregion
}
Application is running. I edit the Position column. Then I switch to the 2nd tab and then the 1st tab again. Position value of edited row is deleted.
If I write data directly to tabItems - it works okay
<Grid>
<TabControl>
<TabItem Header="Tab1">...</TabItem>
<TabItem Header="Tab2">...</TabItem>
</TabControl>
</Grid>
But I need to use DataTemplates for tabItems in my solution.
I have some idea.. Using style, not datatemplate inside editing mode
<DataGridComboBoxColumn Header="Position" SelectedItemBinding="{Binding Position, UpdateSourceTrigger=PropertyChanged}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Position, ElementName=dataGridEx}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Variables, ElementName=dataGridEx}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
In this way everything works okay.
But I have custom control like IntelliSense instead of ComboBox. It is requires to use DataGridTemplateColumn with DataTemplates for CellTemplate and CellEditingTemplate. What Should I do in this case? Maybe I need to create custom DataGridComboBoxColumn?
Can you help me with my issue?

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