MultiBinding with two Comboboxes in different DataContexts - c#

I have the following datagrid that contains two comboboxes each contained in a data template.
<DataGrid x:Name="FilterSelection" HorizontalAlignment="Stretch">
<DataGrid.Resources>
<DataTemplate x:Key="EntityComboboxTemp">
<ComboBox x:Name="EntityCombobox"
ItemsSource="{Binding DataContext.FilterVehicleEntities, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectedEntityCommand, RelativeSource={RelativeSource AncestorType=local:ExportView}}"
CommandParameter="{Binding ElementName=EntityCombobox, Path=SelectedItem}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="AttributeComboboxTemp">
<ComboBox x:Name="AttributeCombobox"
ItemsSource="{Binding DataContext.FilterAttributes, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectedAttributeCommand, RelativeSource={RelativeSource AncestorType=local:ExportView}}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{conv:FilterMultipleConverter}">
<Binding ElementName="EntityCombobox" Path="SelectedItem"/>
<Binding ElementName="AttributeCombobox" Path="SelectedItem"/>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Entity" CellTemplate="{StaticResource EntityComboboxTemp}" />
<DataGridTemplateColumn Header="Entity Attribute" CellTemplate="{StaticResource AttributeComboboxTemp}"/>
</DataGrid.Columns>
</DataGrid>
The problem lies in the multiple binding of the second combobox, namely the line:
Binding ElementName="EntityCombobox" Path="SelectedItem"/>
which should bind the selecteditem of the first combobox to the second combobox as commandparameter. But I always get the Data binding error that EntityCombobox is unknown. How can I set the DataContext for this binding, is this even possible?

My suggestion is to not do so much in your view. I would recommend to do this logic in the ViewModel for the DataGrid.
To start then I would create a view model for holding your filter selections.
Example: (please see comments for where to put logic for selection changes)
public class FilterViewModel
{
private string _vehicleEntity;
public string VehicleEntity
{
get { return _vehicleEntity; }
set
{
_vehicleEntity = value;
//OnPropertyChanged() if you want
}
}
private string _attribute;
public string Attribute
{
get { return _attribute; }
set
{
_attribute = value;
//Add logic here to determine what to do with both Attribute and VehicleEntity
//OnPropertyChanged() if you want
}
}
}
Then setup your overall View's ViewModel to hold a collection of the FilterModel along with the list of Vehicle options and Attribute options.
Example:
public class MainWindowViewModel
{
public ObservableCollection<FilterViewModel> Rows { get; }
public ObservableCollection<string> FilterVehicleEntities { get; }
public ObservableCollection<string> FilterAttributes { get; set; }
public MainWindowViewModel()
{
Rows = new ObservableCollection<FilterViewModel>();
FilterVehicleEntities = new ObservableCollection<string>()
{
"Vehicle 1",
"Vehicle 2",
"Vehicle 3",
};
FilterAttributes = new ObservableCollection<string>()
{
"Attribute 1",
"Attribute 2",
"Attribute 3",
};
}
}
Then make your view less complicated by just directly binding the selection to the properties. You will then get notified of selection changes as soon as the property updates (as marked in the comments for the FilterViewModel).
Example (full xaml):
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
Width="600"
Height="500">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding Rows}">
<DataGrid.Columns>
<DataGridComboBoxColumn
Header="Entity"
SelectedItemBinding="{Binding VehicleEntity, UpdateSourceTrigger=PropertyChanged}">
<!-- property changed so we get the change right after we select-->
<DataGridComboBoxColumn.ElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterVehicleEntities}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterVehicleEntities}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridComboBoxColumn
Header="Entity Attribute"
SelectedItemBinding="{Binding Attribute, UpdateSourceTrigger=PropertyChanged}">
<!-- property changed so we get the change right after we select-->
<DataGridComboBoxColumn.ElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterAttributes}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style
TargetType="ComboBox">
<Setter
Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.FilterAttributes}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

A ComboBox in a DataTemplate cannot refer to a ComboBox that is defined somewhere else using an ElementName binding because they are not in the same namescope.
What you should do is to bind the SelectedItem property of the EntityCombobox to a source property, and then bind the CommandParameter property of the other ComboBox to the same source property. For this to work, the class where the source property is defined must implement the INotifyPropertyChanged interface and raise change notifications and both ComboBoxes must also share the same DataContext.

Related

Displaying a tooltip for each Combobox item when the list is opened C# WPF XAML MVVM

I'm developing an application in C# MVVM
My question is about adding a tooltip for each item that the ComboBox is binded to. Since there are two items only I want it to show the tooltip whenever I open the dropdown and hover my mouse over one of the items , like this:
if I hover my mouse over the first element in the dropDown I'll get a ToolTip with " first item".. and "second item" when I hover over the second element.
ComboBox is placed in DataGridTemplateColumn -> Cell Template -> DataTemplate
<DataGridTemplateColumn Header="PRĄD POJEMNOŚCIOWY [A]" HeaderStyle="{StaticResource PRAD_POJEMNOSCIOWY}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="PradPojemnosciowyComboBox"
SelectedValue="{Binding SelectedItem, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=LiniaWyComboBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"
IsReadOnly="False"
Text="{Binding Prad_pojemnosciowy, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchEnabled="False"
IsSynchronizedWithCurrentItem="True"
PreviewKeyDown="PradPojemnosciowyComboBox_OnPreviewKeyDown">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<Trigger Property="SelectedValue" Value="{x:Null}">
<Setter Property="SelectedIndex" Value="{Binding LiniaWyComboBox}"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
UPDATE
ToolTipLabel.cs:
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace GPZmodel.UserControlsGraphicGenerators
{
public class ToolTipLabel : INotifyPropertyChanged
{
private string _toolTipText;
public string ToolTipText
{
get { return _toolTipText;}
set
{
if (_toolTipText != value)
{
_toolTipText = value;
}
}
}
public ObservableCollection<ToolTipLabel> ToolTipList = new ObservableCollection<ToolTipLabel>()
{
new ToolTipLabel() {ToolTipText = "Nazwa1"} ,
new ToolTipLabel() {ToolTipText = "Nazwa2"} ,
};
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
You could use an ItemContainerStyle:
<ComboBox Name="PradPojemnosciowyComboBox"
SelectedValue="{Binding SelectedItem, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=LiniaWyComboBox, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True"
IsReadOnly="False"
Text="{Binding Prad_pojemnosciowy, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchEnabled="False"
IsSynchronizedWithCurrentItem="True"
PreviewKeyDown="PradPojemnosciowyComboBox_OnPreviewKeyDown">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<Trigger Property="SelectedValue" Value="{x:Null}">
<Setter Property="SelectedIndex" Value="{Binding LiniaWyComboBox}"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock Text="{Binding}" />
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Bind the TextBlock in the ToolTip to whatever property of your data object that you want to display.

ListBox checkbox command binding

I can't figure this out. I thought I had the binding set properly but it's not firing. So I have a View:
<ListBox x:Name="EquipmentViewsListBox"
ItemsSource="{Binding EquipmentViews, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Extended"
BorderThickness="0"
Height="150"
Margin="5,5,10,10">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}, Path=DataContext.ViewSelected}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Content="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to fire a command every time the checkbox in that ListBox is selected. I created a command on my view model like so:
public class SdddViewModel : ViewModelBase
{
public SdddModel Model { get; set; }
public RelayCommand<ViewWrapper> ViewSelected { get; set; }
public SdddViewModel(SdddModel model)
{
Model = model;
ViewSelected = new RelayCommand<ViewWrapper>(OnViewSelected);
}
private void OnViewSelected(ViewWrapper obj)
{
var asd = obj;
}
}
So I understand that when I do a ListBox.ItemTemplate the context for that item becomes the ListBoxItem so in my case a class object ViewWrapper. That works fine with the Name binding for content as well with the IsSelected property. It's the command that is not firing when item is checked. I set the relative ancestor to ListBox and the Path=DataContext but still nothing happens. Ideas?
The problem is that the CommandParameter doesn't match. You Declared it so the CommandParameter is ViewWrapper but you sent a parameter of type CheckBox by using RelativeSource Self. Change the CommandParameter to simply {Binding} which means it sends the DataContext of the ListBoxItem, which is ViewWrapper.
You could have detected this binding error using Snoop.

WPF ContextMenu ignores CanExecute

I have a DataGrid with elements and a ViewModel behind this view.
The ViewModel has a RelayCommand, that implements CanExecuteChanged.
There's a Style for DataGridRow that has a ContextMenu, its MenuItems are bound to the RelayCommand and pass the item as a parameter.
Here's the XAML:
<ContextMenu x:Key="CommentMenu" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}">
<MenuItem Header="Bind to Project" Command="{Binding BindToProjectCommand}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}}"/>
</ContextMenu>
<Style x:Key="DefaultRowStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource CommentMenu}" />
</Style>
...
<DataGrid ItemsSource="{Binding Comments}"
RowStyle="{StaticResource DefaultRowStyle}"
IsReadOnly="True"
CanUserAddRows="False">
Here's the ViewModel:
public MyViewModel()
{
BindToProjectCommand = new RelayCommand<Comment>(BindToProject, CanBindToProject);
}
public bool CanBindToProject(Comment comment)
{
var answer = comment != null && comment.ProjectId == null;
return answer;
}
At runtime CanExecuteChanged is called, the correct comment is passed as a parameter, true is returned, but the menu item is still disabled.
The Output window has no binding errors, so the binding is definitely correct, I can see the correct instance coming.
So the questions are:
1. Why does is the result of CanExecute ignored?
2. How to make it work?
Thank you for your input.
So I found out that the binding of CommandParameter always binds to the last comment in the list. The simplest workaround is to bind SelectedItem to a property in the ViewModel and change the command and the CanBindToProject-method as follows:
<DataGrid ItemsSource="{Binding Comments}"
IsReadOnly="True"
CanUserAddRows="False"
RowStyle="{StaticResource DefaultRowStyle}"
SelectedItem="{Binding SelectedComment}"
SelectionMode="Single"/>
public Comment SelectedComment
public RelayCommand BindToProjectCommand(BindToProject, CanBindToProject)
public bool CanBindToProject()
{
return SelectedComment != null && SelectedComment.ProjectId == null;
}

List box with checkboxes + Command binding not firing in empty space click

I am sure that I am missing something small here, but somehow not able to figure out. I have a list box bound to a collection in my view model. In my datatemplate for listbox item I have a checkbox. The check box has command binding to a ICommand in view model. When displayed some checkbox have text enough to fully cover the width of list box item others do not so a blank space at end is left behind.
The behavior I want is that when the user clicks anywhere in the list box item (even in last empty space) the command should get invoked. The problem is that currently I am only able to invoke the Command in view model if user clicks only in area where checkbox is there (if user clicks on left empty space the command is not invoked)
I have tried various options of using a toggle button with control template as check box. The toggle button covers the whole item space and binds to command as required but when I use check box in the toggle button's control template the Command binding stops working.
Below is my XAML. Any suggestions/pointers should help.
<ListBox ItemsSource="{Binding Values}" SelectionMode="Multiple" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource MetroListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Selected}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<CheckBox Grid.Column="0" IsChecked="{Binding Selected,Mode=OneWay}" Content="{Binding Caption}" Command="{Binding DataContext.ItemSelection, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" >
<CheckBox.CommandParameter>
<MultiBinding Converter="{StaticResource IListDataConverter}">
<Binding Path="."/>
<Binding RelativeSource="{RelativeSource Self}" Path="IsChecked"/>
</MultiBinding>
</CheckBox.CommandParameter>
</CheckBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Girija
Well, simple example without double/triple logic with IsSelected on ListBox and bindings it to CheckBox and also to ViewModel.
In XAML i've create ScrollViewer, ItemsControl.
<ScrollViewer Width="200">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:SelectableObject">
<CheckBox IsChecked="{Binding IsSelected}"
Command="{Binding DataContext.ItemSelection, RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}}">
<CheckBox.Content>
<TextBlock Background="Blue" Text="{Binding Name}"/>
</CheckBox.Content>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
In codebehind:
private ICommand _itemSelection;
public ICommand ItemSelection => _itemSelection ?? (_itemSelection = new DelegateCommand(_ => MessageBox.Show("Click")));
public MainWindow()
{
Items.Add(new SelectableObject("Item 1"));
Items.Add(new SelectableObject("Item 2"));
Items.Add(new SelectableObject("Item 3"));
InitializeComponent();
DataContext = this;
}
public ObservableCollection<SelectableObject> Items { get; } = new ObservableCollection<SelectableObject>();
And simple class
public class SelectableObject : DependencyObject
{
public SelectableObject(string name)
{
Name = name;
}
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register("IsSelected",
typeof (bool), typeof (SelectableObject), new FrameworkPropertyMetadata());
public bool IsSelected
{
get { return (bool) GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public string Name { get; set; }
}

how to Change Datagridcombobox cell to textbox?

I need to change the 1st cell of 1st row to a blank text box.
right now I have the column as datagridviewcombobox column?
<DataGridComboBoxColumn Header="And/Or" Width="60" ItemsSource="{Binding Source={StaticResource PredicateCombinationOperatorsEnumValues}}" SelectedItemBinding="{Binding PredicateCombinationOperator, Mode=TwoWay}" />
<DataGridComboBoxColumn Header="Field" ItemsSource="{Binding Source={StaticResource FieldTypeEnumValues}}" SelectedItemBinding="{Binding FieldType}"/>
<DataGridComboBoxColumn Header="Operator" MinWidth="70" ItemsSource="{Binding Source={StaticResource OperatorsEnumValues}}" SelectedItemBinding="{Binding Operator}"/>
<DataGridTextColumn Header="Value" MinWidth="100" Width="*" Binding="{Binding Expression}"/>
</DataGrid.Columns>
<DataGrid.InputBindings>
<KeyBinding Command="{Binding Source={StaticResource DeleteContextMenuCommand}}" Key="Delete"/>
</DataGrid.InputBindings>
<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding MenuOptions}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Command" Value="{Binding}" />
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}" />
<Setter Property="AutomationProperties.AutomationId" Value="{Binding Name}" />
<Setter Property="AutomationProperties.Name" Value="{Binding Name}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</DataGrid.ContextMenu>
<i:Interaction.Triggers>
<i:EventTrigger EventName="ContextMenuOpening">
<trigger:ContextMenuOpeningTriggerAction/>
</i:EventTrigger>
<i:EventTrigger EventName="SelectionChanged">
<trigger:SelectionChangeTriggerAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
You could specify conditional data template and select the template according to your condition. First we have to inherit class from DataTemplateSelector and define properties of DataTemplate type. Define as much properties as data template you want.Then override the SelectTemplate method to return the datatemplate you want.Check the below sample code
<Window.Resources>
<local:Animals x:Key="animals"/>
<DataTemplate x:Key="TextTemplate">
<TextBox Margin="2" Width="60" />
</DataTemplate>
<DataTemplate x:Key="ComboTemplate" >
<ComboBox Width="60" />
</DataTemplate>
</Window.Resources>
<Grid>
<Controls:DataGrid>
<Controls:DataGrid.Columns>
<Controls:DataGridTemplateColumn Header="And/Or" Width="60">
<Controls:DataGridTemplateColumn.CellTemplateSelector>
<local:CustomTemplateSelector
TextTemplate="{StaticResource TextTemplate}"
ComboTemplate="{StaticResource ComboTemplate}"/>
</Controls:DataGridTemplateColumn.CellTemplateSelector>
</Controls:DataGridTemplateColumn>
<Controls:DataGridComboBoxColumn Header="Field"/>
<Controls:DataGridComboBoxColumn Header="Operator" MinWidth="70" />
<Controls:DataGridTextColumn Header="Value" MinWidth="100" Width="*"/>
</Controls:DataGrid.Columns>
</Controls:DataGrid>
</Grid>
public class CustomTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate
{ get; set; }
public DataTemplate ComboTemplate
{ get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
MyObject obj = item as MyObject;
if (obj != null)
{
// custom logic to select appropriate data template and return
}
else
return base.SelectTemplate(item, container);
}
}
}
for more check here
http://zamjad.wordpress.com/2010/08/23/apply-conditional-data-template-in-data-grid/

Categories

Resources