WPF DataGrid CellTemplateSelector Item - c#

I have a grid bound to a collection of VMs. When using DataTemplateSelector for my DataGridTemplateColumn I'm getting the whole VM as a data item, how do I narrow it down to a specific property value (otherwise I have to create 'DataTemplateSelector' for each VM or use interfaces, with both are too cumbersome) ?
Saw Bind a property to DataTemplateSelector, but it looks like a nasty workaround.

You can use Expressions Trees in a DataTemplateSelector's derived class, that I called PropertyTemplateSelector. Here its code:
public abstract class PropertyTemplateSelector : DataTemplateSelector
{
private Delegate getPropertyValue;
private string propertyName;
private Type itemType;
public string PropertyName
{
get
{
return propertyName;
}
set
{
propertyName = value;
}
}
public Type ItemType
{
get
{
return itemType;
}
set
{
itemType = value;
}
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (ItemType.IsInstanceOfType(item))
{
if (getPropertyValue == null)
{
System.Linq.Expressions.ParameterExpression instanceParameter =
System.Linq.Expressions.Expression.Parameter(item.GetType(), "p");
System.Linq.Expressions.MemberExpression currentExpression =
System.Linq.Expressions.Expression.PropertyOrField(instanceParameter, PropertyName);
System.Linq.Expressions.LambdaExpression lambdaExpression =
System.Linq.Expressions.Expression.Lambda(currentExpression, instanceParameter);
getPropertyValue = lambdaExpression.Compile();
}
return SelectTemplateImpl(getPropertyValue.DynamicInvoke(item), container);
}
return base.SelectTemplate(item, container);
}
protected abstract DataTemplate SelectTemplateImpl(object propertyValue, DependencyObject container);
}
You can extend this class with your own logic, just by implementing the SelectTemplateImpl method. As you can see the PropertyTemplateSelector narrows the item object down to a specific property value (which is passed to the SelectTemplateImpl method). For example I created a NameTemplateSelector in this way:
public class NameTemplateSelector : PropertyTemplateSelector
{
protected override DataTemplate SelectTemplateImpl(object propertyValue, DependencyObject container)
{
string name = (string)propertyValue;
if (name != null && name.StartsWith("A", StringComparison.OrdinalIgnoreCase))
{
return (DataTemplate)App.Current.MainWindow.FindResource("VipName");
}
return (DataTemplate)App.Current.MainWindow.FindResource("NormalName");
}
}
The you can easly use these template selectors in your XAML
<Window.Resources>
<DataTemplate x:Key="NormalName">
<TextBlock Text="{Binding Mode=OneWay, Path=Name}" Margin="3" />
</DataTemplate>
<DataTemplate x:Key="VipName">
<TextBlock Text="{Binding Mode=OneWay, Path=Name}" Margin="3" Foreground="Red" FontWeight="Bold" />
</DataTemplate>
<DataTemplate x:Key="NormalSurname">
<TextBlock Text="{Binding Mode=OneWay, Path=Surname}" Margin="3" />
</DataTemplate>
<DataTemplate x:Key="VipSurname">
<TextBlock Text="{Binding Mode=OneWay, Path=Surname}" Margin="3" Foreground="Green" FontStyle="Italic" />
</DataTemplate>
<local:NameTemplateSelector x:Key="NameTemplateSelector" PropertyName="Name" ItemType="{x:Type local:Person}" />
<local:SurnameTemplateSelector x:Key="SurnameTemplateSelector" PropertyName="Surname" ItemType="{x:Type local:Person}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Path=People, Mode=OneWay}" AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name"
CellTemplateSelector="{StaticResource NameTemplateSelector}" />
<DataGridTemplateColumn Header="Surname"
CellTemplateSelector="{StaticResource SurnameTemplateSelector}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
I hope it can help you.

Related

WPF listbox hierarchial item template

I have a list box that is bound to hierarchial collection. The data has list menu items. There are two types of menus. First one is grouping menu, which will have child items. Second one is menu items which can be selected and this one does not have any children.
I have a property names HasChildren to distinguish between different type of menu. I want to apply different type of template for both types of menu. I want to show buttons for selectable(second) menus and tree view for menu with chidlren.
<ListBox ItemsSource="{Binding UserMenus}">
<ListBox.ItemTemplate>
<DataTemplate>
<TreeViewItem ItemsSource="{Binding Children}" Header="{Binding Name}">
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel>
<Button Width="250" Content="{Binding Name}" ToolTip="{Binding Name}" Style="{StaticResource MaterialDesignRaisedButton}"
Command="{Binding DataContext.TemplateCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding Id}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I tried this approach but it is not working for top level menu items, it is displaying tree view item for both types of menus.
My hierarchial data class.
public class MenuModel : ObservableObject, IMenuModel
{
string _name, _description, _id;
public MenuModel()
{
Children = new ObservableCollection<IMenuModel>();
}
public string Name { get => _name; set => SetProperty(ref _name, value); }
public string Description { get => _description; set => SetProperty(ref _description, value); }
public string Id { get => _id; set => SetProperty(ref _id, value); }
public bool HasChildren
{
get
{
bool returnValue = false;
if (Children != null && Children.Count > 0)
{
returnValue = true;
}
return returnValue;
}
}
public ObservableCollection<IMenuModel> Children { get; set; }
}
Help me to apply template properly to achieve desired result.
You can try this:
Xaml
<ListBox ItemsSource="{Binding UserMenus}">
<ListBox.Resources>
<HierarchicalDataTemplate x:Key="DataTemplate2" ItemsSource="{Binding Children}">
<StackPanel>
<Button Width="250" Content="{Binding Name}" ToolTip="{Binding Name}" Style="{StaticResource MaterialDesignRaisedButton}"
Command="{Binding DataContext.TemplateCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding Id}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="DataTemplate1">
<TreeViewItem ItemsSource="{Binding Children}" Header="{Binding Name}"/>
</DataTemplate>
</ListBox.Resources>
</ListBox>
DataTemplateSelector
public sealed class MenuModelSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is MenuModel menuModel)
{
var resourceKey = menuModel.HasChildren ? "DataTemplate2" : "DataTemplate1";
if (container is FrameworkElement element && element.FindResource(resourceKey) is DataTemplate dataTemplate)
{
return dataTemplate;
}
}
return base.SelectTemplate(item, container);
}
}

Which is the best way to display combobox on a cell if a condition is verified?

So here is my problem: I have to build a grid on my view (it can be a datagrid or a gridview, i have no particular preference). I know the number of columns, but i have to build the row dinamically. The problem is that there are some rows that must display a combobox on the third column if the datatype of the data inside the first column is different from "string", otherways they have a simple textbox. I know that there is a control named "template selector" which can do stuff like this, but since i never used it, i wonder if this is the best way to approach my problem.
Following up on the comment, if you are willing to use ItemsControl in place of your grid, one way to achieve the behavior in question is as follows:
View:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding MyItems, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Width="150" Content="{Binding FirstProperty}"/>
<ComboBox Width="150"
Visibility="{Binding HasCombobox, Converter={StaticResource BoolToVis}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Code-behind + view-model:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
public class MainWindowViewModel
{
public MainWindowViewModel()
{
MyItems = new List<DisplayableItem>
{
new DisplayableItem { FirstProperty = "Some string" },
new DisplayableItem { FirstProperty = 60 },
new DisplayableItem { FirstProperty = "Also a string" },
};
}
public IEnumerable<DisplayableItem> MyItems { get; }
}
public class DisplayableItem
{
public object FirstProperty { get; set; }
public bool HasCombobox => !(FirstProperty is string);
}
Without knowing any details about what you are trying to do, you could for example use a DataGridTemplateColumn with a CellTemplateSelector/CellEditingTemplateSelector.
The following sample code should give you the idea:
<DataGrid x:Name="dg">
<DataGrid.Resources>
<DataTemplate x:Key="dtA">...</DataTemplate>
<DataTemplate x:Key="dtB">...</DataTemplate>
<local:Selector x:Key="selector" TemplateA="{StaticResource dtA}" TemplateB="{StaticResource dtA}" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding A}" />
<DataGridTextColumn Binding="{Binding B}" />
<DataGridTemplateColumn CellEditingTemplateSelector="{StaticResource selector}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding C}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
public class Selector : DataTemplateSelector
{
public DataTemplate TemplateA { get; set; }
public DataTemplate TemplateB { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
System.Data.DataRowView dr = item as DataRowView;
//return TemplateA or TemplateB based on your logic
return base.SelectTemplate(item, container);
}
}

WPF - How to implement two-way data binding with the dynamically created control?

I'm writing a program that dynamically creates Control based on the data type of the properties extracted using reflection. Here is the view in subject for examination.
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<UserControl FontSize="14" Content="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}}"></UserControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I created an item template for the items in ListView. Each row consists of two elements; the label and the dynamically created control.
For instance, if the PropertyValue is a boolean, then the dynamically created control will be a checkbox. If the PropertyValue is a string, then the dynamically created control will be a TextBox. If the PropertyValue is a list of FileInfo, then a separate window will be created with another ListView and browse button with OpenFileDialog.
I was able to accomplish the dynamically created control by creating a class that implements IValueConverter and which is utilized as specified in the XAML. The PropertyValueConverter converts the PropertyValue into a dynamically created control by inspecting its data type.
My problem is when the CheckBox is checked, there was no event raised and the ViewModel is not modified by its changes. I suspect because the binding in the XAML was made to the UserControl and not to its child control which happens to be a CheckBox. Although it is possible to bind the IsChecked programmatically in the PropertyValueConverter, is there a better way to solve this?
------- Revision 1 -------
public class PropertyControl: INotifyPropertyChanged
{
public string PropertyName { get; set; }
private object propertyValue;
public object PropertyValue
{
get { return propertyValue; }
set
{
propertyValue = value;
OnPropertyChanged(nameof(PropertyValue));
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
/// <summary>
/// Dynamically converts between value and control given a data type - control mapping.
/// </summary>
class PropertyValueConverter: IValueConverter
{
/// <summary>
/// Converts from value to control.
/// </summary>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (int))
return new NumberTextBox {Text = value.ToString()};
if (targetType == typeof (string))
return new TextBox {Text = value.ToString()};
if (targetType == typeof (bool))
return new CheckBox {IsChecked = (bool) value};
throw new Exception("Unknown targetType: " + targetType);
}
/// <summary>
/// Converts from control to value.
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof (NumberTextBox))
return (value as NumberTextBox).Value;
if (targetType == typeof(TextBox))
return (value as TextBox).Text;
if (targetType == typeof(CheckBox))
return (value as CheckBox).IsChecked;
throw new Exception("Unknown targetType: " + targetType);
}
}
------- Revision 2 -------
public partial class SettingsWindow : Window
{
public BindingList<SettingViewModel> ViewModels { get; set; }
private SettingsManager settingsManager = new SettingsManager(new SettingsRepository());
public SettingsWindow()
{
InitializeComponent();
// Reloads the data stored in all setting instances from database if there's any.
settingsManager.Reload();
// Initialize setting view model.
ViewModels = SettingViewModel.GetAll(settingsManager);
}
private void ResetButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.Reload();
}
private void SaveButton_OnClick(object sender, RoutedEventArgs e)
{
settingsManager.SaveChanges();
}
}
--- Tab Control ---
<TabControl Name="ClassTabControl" TabStripPlacement="Left" ItemsSource="{Binding ViewModels}">
<TabControl.Resources>
<utilities:PropertyValueConverter x:Key="PropertyValueConverter" />
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}"
Margin="8" FontSize="14"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding PropertyControls}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"></TextBlock>
<CheckBox FontSize="14" IsChecked="{Binding Path=PropertyValue, Converter={StaticResource PropertyValueConverter}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="8" HorizontalAlignment="Center">
<Button Name="ResetButton" Padding="4" Content="Reset" FontSize="14" Margin="4"
Click="ResetButton_OnClick"></Button>
<Button Name="SaveButton" Padding="4" Content="Save" FontSize="14" Margin="4"
Click="SaveButton_OnClick"></Button>
</StackPanel>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
A much easier way is to create templates based on the type of your property. First of all you have to add the system namespace to access all the basic types:
xmlns:System="clr-namespace:System;assembly=mscorlib"
Now you can get rid of your converter and do it all in XAML like:
<DataTemplate>
<StackPanel x:Name="itemStackPanel" Orientation="Horizontal" Margin="8">
<!-- General part -->
<TextBlock Text="{Binding PropertyName}" FontSize="14" Width="400"/>
<!-- Specific (property based) part -->
<ContentPresenter Content="{Binding PropertyValue}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type System:String}">
<TextBlock Text="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Boolean}">
<CheckBox IsChecked="{Binding ElementName=itemStackPanel, Path=DataContext.PropertyValue}"/>
</DataTemplate>
<!-- ... -->
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</DataTemplate>
You simply create a template for every possible type like you need it. The ContentPresenter selects the right template based on the type of PropertyValue. Since you are going to bind to a parent from out of your template you have to use a element to bind on PropertyValue (described in Access parent DataContext from DataTemplate).
/edit Ok, some one was faster :/
Here's the example (without INotifyPropertyChanged because I did not want to write too much code ;))
public interface IViewModel
{
string PropertyName { get; set; }
}
public class StringViewModel : IViewModel
{
public string PropertyName { get; set; }
public string Content { get; set; }
}
public class BooleanViewModel : IViewModel
{
public string PropertyName { get; set; }
public bool IsChecked { get; set; }
}
public class MainViewModel
{
public ObservableCollection<IViewModel> ViewModels { get; set; }
public MainViewModel()
{
ViewModels = new ObservableCollection<IViewModel>
{
new BooleanViewModel {PropertyName = "Bool", IsChecked = true },
new StringViewModel {PropertyName = "String", Content = "My text"}
};
}
}
<Window x:Class="WpfApplication2.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:WpfApplication2"
mc:Ignorable="d"
xmlns:viewModel="clr-namespace:WpfApplication2"
Title="MainWindow">
<Grid>
<ListView ItemsSource="{Binding ViewModels}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Margin="8">
<TextBlock Text="{Binding PropertyName}" />
<ContentControl FontSize="14" Content="{Binding .}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModel:StringViewModel}">
<TextBox Text="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:BooleanViewModel}">
<CheckBox IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>

Bind selected value of a multicolumn combobox inside DataGrid

Working Example :
I have a table called groups as shown below :
After looking at the image above I think you might have understood that primary key and foreign key exist in the same table. I think this is what developers call cyclic reference.
In MainWindow.xaml I have a DataGrid which contains three columns namely Group Name, Parent Name, Description. The xaml looks like :
<Window .......>
<Window.DataContext>
<self:MainWindowViewModel />
</Window.DataContext>
<DataGrid ItemsSource="{Binding Groups}" TabIndex="1">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Group Name" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding GroupName}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding GroupName}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Parent" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ParentID, Converter={StaticResource GroupIDToGroupNameConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.GroupsCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
SelectedValue="{Binding ParentID}"
SelectedValuePath="GroupID"
DisplayMemberPath="GroupName"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Description" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Description}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</power:PowerDataGrid.Columns>
</power:PowerDataGrid>
</Window>
Now I have a ViewModel called MainWindowViewModel
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
SampleDBContext sampleDBContext = new SampleDBContext();
Groups = new ObservableCollection<Group>();
GroupsCollection = new ObservableCollection<Group>(from g in sampleDBContext.Groups select g);
}
private ObservableCollection<Group> _groups;
public ObservableCollection<Group> Groups
{
get
{
return _groups;
}
set
{
_groups = value;
OnPropertyChanged("Groups");
}
}
private ObservableCollection<Group> _groupsCollection;
public ObservableCollection<Group> GroupsCollection
{
get
{
return _groupsCollection;
}
set
{
_groupsCollection = value;
OnPropertyChanged("GroupsCollection");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertryName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertryName));
}
}
#endregion
}
GroupIDToGroupName.cs //Converter
public class GroupIDToGroupName : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
SampleDBContext sampleDBContext = new SampleDBContext();
return (from g in sampleDBContext.Groups
where g.GroupID == (int)value
select g.GroupName).FirstOrDefault();
}
else
{
return "";
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SampleDBContext sampleDBContext = new SampleDBContext();
return (from g in sampleDBContext.Groups
where g.GroupName == (string)value
select g.GroupID).FirstOrDefault();
}
}
In App.xaml :
<self:GroupIDToGroupName x:Key="GroupIDToGroupNameConveerter" />
My Case (Very similar to above sample):
I just want to use a Multi-Column ComboBox instead of simple ComboBox inside DataGrid.
I have two tables :
Now I have set up my code exactly as the above mentioned code.
I have added an extra class called GroupIDAndNameWithCorrespondingEffect like :
public class GroupIDAndNameWithCorrespondingEffect : INotifyPropertyChanged
{
private int _groupID;
public int GroupID
{
get
{
return _groupID;
}
set
{
_groupID = value;
OnPropertyChanged("GroupID");
}
}
private string _groupName;
public string GroupName
{
get
{
return _groupName;
}
set
{
_groupName = value;
OnPropertyChanged("GroupName");
}
}
private string _correspondingEffect;
public string CorrespondingEffect
{
get
{
return _correspondingEffect;
}
set
{
_correspondingEffect = value;
OnPropertyChanged("CorrespondingEffect");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Changes in my ViewModel :
I removed the property GroupsCollection and all its references and added a new property called GroupIDAndNamesWithCorrespondingEffects as below :
private ObservableCollection<GroupIDAndNameWithCorrespondingEffect> _groupIDAndNamesWithCorrespondingEffects;
public ObservableCollection<GroupIDAndNameWithCorrespondingEffect> GroupIDAndNamesWithCorrespondingEffects
{
get
{
return _groupIDAndNamesWithCorrespondingEffects;
}
set
{
_groupIDAndNamesWithCorrespondingEffects = value;
OnPropertyChanged("GroupIDAndNamesWithCorrespondingEffects");
}
}
And in the Constructor :
List<GroupIDAndNameWithCorrespondingEffect> _GroupIDAndNamesWithCorrespondingEffects = (
from g in sampleDBContext.Groups
select new GroupIDAndNameWithCorrespondingEffect
{
GroupID = g.GroupID,
GroupName = g.GroupName,
CorrespondingEffect = g.Effect.Effect1
}
).ToList();
GroupIDAndNamesWithCorrespondingEffects
= new ObservableCollection<GroupIDAndNameWithCorrespondingEffect>(
_GroupIDAndNamesWithCorrespondingEffects.Where
(
u => !GetAllChildren(25)
.Select(x => x.GroupID)
.Contains(u.GroupID)
).ToList()
);
In my MainWindow.xaml I have added a resource as follows :
<Window.Resources>
<CollectionViewSource x:Key="GroupNamesWithCorrespondingEffectsCollection" Source="{Binding GroupIDAndNamesWithCorrespondingEffects}" />
</Window.Resources>
Inside Grid's Resources :
<Grid.Resources>
<CompositeCollection x:Key="Items">
<ComboBoxItem IsEnabled="False" Background="#FF2A2A2A" Foreground="White">
<Grid TextElement.FontWeight="Bold" >
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" />
<ColumnDefinition Width="50" />
<ColumnDefinition SharedSizeGroup="B" />
</Grid.ColumnDefinitions>
<Grid.Children>
<TextBlock Grid.Column="0" Text="Group Name" />
<TextBlock Grid.Column="2" Text="Effect" />
</Grid.Children>
</Grid>
</ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource GroupNamesWithCorrespondingEffectsCollection}}" />
</CompositeCollection>
<DataTemplate DataType="{x:Type self:GroupIDAndNameWithCorrespondingEffect}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" />
<ColumnDefinition Width="50" />
<ColumnDefinition SharedSizeGroup="B" />
</Grid.ColumnDefinitions>
<Grid.Children>
<TextBlock Grid.Column="0" Text="{Binding GroupName}" />
<TextBlock Grid.Column="2" Text="{Binding CorrespondingEffect}" />
</Grid.Children>
</Grid>
</DataTemplate>
</Grid.Resources>
And I changed the ItemsSource of ComboBox to ItemsSource="{DynamicResource Items}".
Problems :
When I run the program ComboBox displays all the items correctly. Also, two columns with headers are displayed. Its working fine, but when I press Enter or TAB, then focus remains in the same cell and the comboBox's text displays namespace of GroupIDAndNameWithCorrespondingEffect
Here is the image of Problem :
Sample:
Incase if somebody wants to check the sample then it is available here. And database files are available here.
Got it!!!!
I declared the resource with key="Items" in Grid. So, when I Check for ComboBox.SelectedIndex in PreviewKeyDown event of DataGrid, it gives me -1 and so my logic works as unexpected. Also, at this time I get ComboBox.Items.Count = 0.
So I just changed the place of declaration of the resource. I mean I deleted Grid.Resources Section and in ComboBox.Resources section I wrote the same code. Now it is working fine. Now in PreviewKeyDown of DataGrid, I get the expected SelectedIndex of the ComboBox as well as ComboBox.Items.Count is equal to the Count in Source.
I don't know why this happens? As I have used it as DynamicResource I expect it to work even if it is declared in Grid.Resources section.
Maybe ... maybe your combobox just needs IsEditable = false. IsEditable = True causes comboboxes to show namespaces instead of the text.
One way to solve this is in http://www.shujaat.net/2010/08/wpf-editable-combobox-with-datatemplate.html
TextSearch.TextPath="GroupName"
Another way would be to provide a DataTemplate to the ComboBox's ItemTemplate
<ComboBox ItemTemplate="{StaticResource myDataTemplate}"/>

TreeView with multiple leaf data types in WPF

I am trying to create a TreeView with the following hierarchy:
Device1
--File1
--File2
--Hook1
--Hook2
Device2
--File1
--File1
Device3
--Hook1
So basically The Root level node is a device with children being File and Hook. I have created the following tree with the hierarchical data template
<TreeView ItemsSource="{Binding Devices}">
<HierarchicalDataTemplate DataType="{x:Type datamodel:Device}" ItemsSource="{Binding Files}">
<TextBlock Margin="5,5,0,0" Text="{Binding DeviceName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type datamodel:File}">
<TextBlock Margin="5,0,0,0" Text="{Binding FileName}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
Class Devices
{
public string DeviceName
{
get;set;
}
public string List<File> Files
{
get;set;
}
public string List<Hook> Hooks
{
get;set;
}
}
public class File
{
public string FileName
{
get;set;
}
public string FileType
{
get;set;
}
public string Location
{
get; set;
}
}
public class Hook
{
public string HookName
{
get;set;
}
public string Type
{
get;set;
}
}
I am able to add only one Datatemplate in the ItemTemplate of the HierarchicalDataTemplate. How do I specify two data types under a single HierarchicalDataTemplate??
You will have to use the converter here which will return the CompositeCollection which will contain both files and hooks. You will use this converter with ItemsSource of HierarchicalDataTemplate DataType="{x:Type datamodel:Device}"
Then you just need to define two datatemplates for File and Hook data type.
Thanks
I tried with the CompositeCollection but realized that the bound objects were converted to base types. So I used it in combination of DataTemplateSelector.
The following solution worked for me.
<TreeView ItemsSource="{Binding Devices}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type datamodel:Devices}" ItemTemplateSelector="{StaticResource LeafDataTemplateSelector}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource CompositeCollectionConverter}">
<Binding Path="Files" />
<Binding Path="Hooks"/>
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Height="25" Orientation="Horizontal">
<Image Height="20" Width="20" Source="Device.png"/>
<TextBlock Margin="5,5,0,0" Text="{Binding Device}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="FileKey" DataType="{x:Type datamodel:File}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation File">
<Image Height="20" Width="20" Source="File.png" />
<TextBlock Text="{Binding FileName}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="HookKey" DataType="{x:Type datamodel:Hook}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation Hook">
<Image Height="20" Width="20" Source="Hook.png" />
<TextBlock Margin="5,5,0,0" Text="{Binding HookName}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
The have used the template selector to select the appropriate template based on the key.
public class LeafDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is InstallationManifesFile)
return
element.FindResource("FileKey")
as DataTemplate;
else if (item is InstallationManifestHook)
return element.FindResource("HookKey")
as DataTemplate;
}
return null;
}
}
public class CompositeCollectionConverter : IMultiValueConverter
{
public object Convert(object[] values
, Type targetType
, object parameter
, System.Globalization.CultureInfo culture)
{
var res = new CompositeCollection();
foreach (var item in values)
if (item is IEnumerable && item != null)
res.Add(new CollectionContainer()
{
Collection = item as IEnumerable
});
return res;
}
public object[] ConvertBack(object value
, Type[] targetTypes
, object parameter
, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Categories

Resources