Datacontext not available in DataGridTemplateColumn - c#

I have the following grid:
<DataGrid
x:Name="CandiesDataGrid"
ItemsSource="{Binding Candies}"
SelectedItem="{Binding SelectedCandy}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding CandySelectedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTextColumn KeyboardNavigation.IsTabStop="False" IsReadOnly="True" Width="100" Header="{l:LocText Candy_Prop1}" Binding="{Binding CandyInfo.Name}"/>
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="IsConfirmed" Grid.Column="0"
Style="{StaticResource CandyCheckBox}"
IsChecked="{Binding IsConfirmed, Mode=TwoWay}"
Margin="-75 0 0 0"
Command="{Binding IsConfirmedCommand}">
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
My property uses the OnPropertyChanged. Not only it does not change the value of IsConfirmed but also does not executes the ICommand IsConfirmedCommand.
I searched on the internet and it seems DataGridTemplateColumn loses the ItemSource of the datagrid.
I did try to put RelativeSource in after the mode=TwoWay on my checkbox but it does not work.
Is there any way to have access to the ItemSource in my TemplateColumn?
EDIT:
//Properties
public ObservableCollection<Candy> Candies{ get; } = new ObservableCollection<Candy>();
public Candy SelectedCandy { get { return _selectedCandy; } set { SetProperty(ref _selectedCandy, value); } } //SetProperty acts as OnPropertyChanged
private Candy _selectedCandy;
//Constructor:
public CandyClass()
{
IsConfirmedCommand = new DelegateCommand(IsConfirmedCommand_Execute);
}
//Method
private void IsConfirmedCommand_Execute()
{
//Doing something
}

Inside your CellTemplate, the DataContext is the DataGrid row, whatever that may be (Candy in this case). So by default, that Candy instance will be the Source property of any Binding in that DataTemplate. That's where the binding will look for the property named in the Path (IsConfirmed and IsConfirmedCommand, in this case).
That's what you want: You've got more than one row in the grid, and the row is what you care about in a cell, usually. That or the field: But very often a cell template will want to look at more than one field, so they give you the whole row.
But in this case, you want to go back up and grab something off the parent viewmodel. Viewmodels have no natural parent/child hierarchy, though you could give them one if you wanted: Candy could have a Parent property that had reference to the viewmodel that owns the Candies collection. If you did, you could bind like this:
Command="{Binding Parent.IsConfirmed}"
But that's not common practice. I don't know if it's a particularly great idea or not.
One reason we don't need to do that is we can tell the binding to use a different source instead. UI elements do have a natural parent/child hierarchy, and bindings can navigate it. If you’re doing things right, your parent viewmodel will be the DataContext of something up there somewhere.
{Binding Path=DataContext.IsConfirmed,
RelativeSource={RelativeSource AncestorType=DataGrid}}
"Walk the UI tree upwards until you find a DataGrid. That's your source. Now, once you have a source, find the source object's DataContext property, if any. If it's got a DataContext, take the value of DataContext and look on that object for some property called IsConfirmed."
DataGrid has a DataContext property. Since your binding to Candies worked, we know that DataContext must be your class that has a Candies property. You assure me that class has IsConfirmed as well.
Hence:
<DataTemplate>
<CheckBox
Style="{StaticResource CandyCheckBox}"
IsChecked="{Binding DataContext.IsConfirmed,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
Margin="-75 0 0 0"
Command="{Binding DataContext.IsConfirmedCommand,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
/>
</DataTemplate>

Related

How do I bind a comboBox to a different ItemsSource than it's parent DataGrid's ItemSource in a UWP project?

I am using MVVM and bind a DataGrid to an ObservableCollection<CustomClass>. I can successfully do that, however I want one of the DataGrid columns to be a ComboBox column, with items source List<ComboBoxValues>. Also another column must be a ToggleSwitch where a Command with parameter is fired when it's IsOn property changes value.
In the ViewModel:
public ObservableCollection<CustomClass> DataGridData { get; set; } = new ObservableCollection<CustomClass>();
public List<ComboBoxValues> ListValues { get; set; } = new List<ComboBoxValues>();
public MyICommand<ToggleSwitch> ToggleSwitchToggled_Command { get; private set; }
The problem I am getting is that the DataGrid's ItemsSource Binding is overriding the Binding path for the CombobBox's ItemsSource, as well as the Command's Binding. ListValues and ToggleSwitchToggled_Command is looked for within DataGridData.
For example:
Error: BindingExpression path error: 'ListValues' property not found on 'UWPProject.ViewModels.DataGridData'. BindingExpression: Path='ListValues' DataItem='UWPProject.ViewModels.DataGridData'; target element is 'Windows.UI.Xaml.Controls.ComboBox' (Name='null'); target property is 'ItemsSource' (type 'Object')
In Xaml:
<controls:DataGrid GridLinesVisibility="All"
AlternatingRowBackground="Gray" AutoGenerateColumns="False"
ItemsSource="{Binding DataGridData,Mode=TwoWay}">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Header="TextOne" Binding="{Binding aPropertyOneInDataGridData,Mode=TwoWay}"/>
<controls:DataGridTextColumn Header="TextTwo" Binding="{Binding aPropertyTwoInDataGridData,Mode=TwoWay}"/>
<controls:DataGridTemplateColumn Header="ComboBoxHeader">
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<ComboBox ItemsSource="{Binding ListValues}"
SelectedValue="{Binding aPropertyThreeInDataGridData,Mode=TwoWay}"
PlaceholderText="Select Action">
</ComboBox>
</StackPanel>
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
<controls:DataGridTemplateColumn Header="ToggleSwitch_Header">
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<ToggleSwitch x:Name="ToggelSwitch_Run"
IsOn="{Binding aPropertyFourInDataGridData,Mode=TwoWay}">
<interact:Interaction.Behaviors>
<interactcore:EventTriggerBehavior EventName="Toggled">
<interactcore:InvokeCommandAction Command="{Binding ToggleSwitchToggled_Command}"
CommandParameter="{Binding}"/>
</interactcore:EventTriggerBehavior>
</interact:Interaction.Behaviors>
</ToggleSwitch>
</StackPanel>
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
</controls:DataGrid.Columns>
</controls:DataGrid>
So I guess, the question is, how do I split the "source" or path where the Binding is looked for within the parent DataGrid. There are many WPF questions and answers on this, such as here and here. however I can't use DataContext, as I get the error that "DataContext is not supported in a UWP project." I can't find a solution for a UWP project for this problem. Also, I am using MVVM and no code behind, so I don't believe I can use x:Binding. I don't want to use code behind either.
Please help.
For your scenario, we suggest use DataGridComboBoxColumn to replace custom celltemplate. Then you could use x:bind markup extension to bind list property directly.
Because DataGridComboBoxColumn ItemsSource property could not access data source sub property directly, If you already make ListValues property for the Page class, you could also use x:bind to access like the following.
<controls:DataGridComboBoxColumn
Width="*"
Binding="{Binding p2}"
Header="Link"
ItemsSource="{x:Bind ListValues ,Mode=OneWay}"
Tag="Link"
/>
If you do want to make custom celltemplate, you could insert the ListValues property into your CustomClass. For more detail please refer this case reply.

Populating DataGridComboBox in Grid with SelectedItem

I am trying to get a DataGridComboBoxColumn (Or DataGridTemplateColumn with a ComboBox) to populate a list of ports and default each row to the port that is already stored in the database. I have got a DataGridTemplateColumn with a ComboBox populating the Ports, but I cannot seem to get it to select what is already stored in the database.
I'm using Entity Framework and I have 2 tables, 'Route' and 'Port'. 'Route' has 2 foreign keys for a 'Destination Port' and a 'Arrival Port'.
I have 2 ObervableCollections, one for the list of ports, and another for the list of routes. In the routes collection there is a 'Port1' and 'Port2' for Destination/Arrival respectively.
This is what I currently have:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.PortCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="PortName"
SelectedItem="{Binding DataContext.RouteCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectedValue="{Binding DataContext.Port1, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"
SelectedValuePath="PortId">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
But as I said it is only listing the available ports, and is otherwise blank. Sorry if I haven't made myself too clear, I'm not very good at articulating, generally.
Here are my collections:
public ObservableCollection<Port> PortCollection { get; set; }
//List of Routes
private ObservableCollection<Route> _RouteCollection;
public ObservableCollection<Route> RouteCollection
{
get { return _RouteCollection; }
set
{
_RouteCollection = value;
Set(() => RouteCollection, ref _RouteCollection, value);
}
}
There's clearly something obvious wrong as this must be quite a common thing to do, but I've been going mad for 4 hours on this just getting this far! :(
Many thanks
"Port1" is supposed to be a property of the item in the DataGrid's Items collection. It should have the same type as the PortId property of the Port class.
You can then bind the SelectedValue property directly to it like this:
<ComboBox ItemsSource="{Binding DataContext.PortCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="PortName"
SelectedValue="{Binding Port1}"
SelectedValuePath="PortId">
The default DataContext of an element in the CellTemplate of a DataGridTemplateColumn is the corresponding item in the Items/ItemsSource collection of the parent DataGrid.
Note that if the type of the Port1 property is Port you should use the SelectedItem property instead of SelectedValue:
<ComboBox ItemsSource="{Binding DataContext.PortCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="PortName"
SelectedItem="{Binding Port1}">

How to pass MenuItem as command parameter for its child control

I have something like below. For MenuItem, here I am passing an object of that MenuItem as a CommandParameter. This works fine for me. My MenuItem holds a RadioButton and I want to use the MenuItem CommandParameter value for this RadioButton. Could anyone please help me how to do this. Thanks in Advance.
<MenuItem Header="Name"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding SortCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
<MenuItem.Icon>
<RadioButton VerticalAlignment="Center"
Margin="3"
IsChecked="True"
GroupName="sort"
Command="{Binding SortCommand}"
CommandParameter="..." />
</MenuItem.Icon>
</MenuItem>
Now Command is executing only when I select the MenuItem. I want to do the same when user selects the RadioButton also. Below is the code which I am using for this.
public void OnSortCommandExecuted(object menuItem)
{
MenuItem menu = menuItem as MenuItem;
if (menu != null)
{
((RadioButton)menu.Icon).IsChecked = !((RadioButton)menu.Icon).IsChecked;
this.eAggregator.GetEvent<ImagesSortedEvent>().Publish(menu.Header.ToString());
}
}
Like I said in the comments as well, it's not a good practise to pass on UI component as CommandParameter to ViewModel since ViewModel shouldn't know about View.
I would suggest you to have proper binding in ViewModel. Create a bool property in ViewModel and bind with IsChecked DP of radioButton. That ways you don't have to pass any CommandParameter from View, simply check the status of bool property from command execute method.
Now, that why MenuItem can't be accessed from RadioButton?
RadioButton doesn't lie in same Visual tree as that of MenuItem.
So, you can't use RelativeSource to travel upto MenuItem. Also ElementName binding won't work here since this to work both elements should lie in same Visual Tree.
You might find over net to use x:Reference in such cases where two elements doesn't lie in same Visual tree but that won't work here since it will create cyclic dependency.
Last thing, you have to resort with it to use Freezable class object to hold an instance of MenuItem and use that resource in your bindings.
First of all you need to define class deriving from Freezable:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object),
typeof(BindingProxy));
}
and you can use it from XAML like this to pass MenuItem:
<MenuItem Header="Name"
x:Name="menuItem"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Command="{Binding SortCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}">
<MenuItem.Resources>
<local:BindingProxy x:Key="proxy"
Data="{Binding Source={x:Reference menuItem}}"/>
</MenuItem.Resources>
<MenuItem.Icon>
<RadioButton VerticalAlignment="Center"
Margin="3"
IsChecked="True"
GroupName="sort"
Command="{Binding SortCommand}"
CommandParameter="{Binding Data.CommandParameter,
Source={StaticResource proxy}}"/>
</MenuItem.Icon>
</MenuItem>
Ofcourse you need to declare local namespace in XAML.
PS - I would still insist to use first approach to define proper bindings in ViewModel.
UPDATE
If MenuItem is placed under ContextMenu, then RelativeSource binding won't be possible. Approach described above will work in that case.
But in case you are placing MenuItem directly as child of some control (like Menu), RelativeSource binding will work:
CommandParameter="{Binding CommandParameter,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}}"

ComboBox in DataGrid: ItemSource bound to dynamic resource, how to set default row?

I'm working in a C# project [for school], using WPF and implementing MVP. In this code, I've got a DataGrid showing a list of divers. The first column is the Name, and the second column shall show 'DivingType'. DivingType is a built in object, which has a property ID, such as 103A. There are about 400 of these, stored in a list, and each Diver ('row') has a Dives (List<Dive>) Property, and each of these Dives has a divingType property.
What we want to have, is that this column will by default show the DivingType.ID associated with the diver, but that the dropdown list shall contain ALL diving types, such that you shall be able to change it from there [and update the diver object]. To further complicate it, this is one of many views which we add to our window as UserControls.
With that said, here is the code. I've tried to cut out unnecessary clutter which I'm certain has no impact on the result.
<Window ...>
<Window.Resources>
<local:Presenter x:Key="myPresenter"/>
</Window.Resources>
<DockPanel DataContext="{StaticResource myPresenter}">
<UserControl ...>
<DataGrid ItemsSource="{Binding DiverList}" x:Name="datagrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="1*" Binding="{Binding Name}"/>
<DataGridTemplateColumn Header="Diving type" Width="1*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl},
Path=DataContext.DivingTypes}"
DisplayMemberPath="ID"
SelectedValue="{Binding Dives[0]}"
SelectedValuePath="divingType">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
</DockPanel>
</Window>
When the program runs I get all DivingTypes.ID inside the combobox, but no selected value. The code does not put any related errors into the output window. I believe that what happens is that it calls DivingType.Equals but passing the DataContext for the row (the Diver) instead of the SelectedValuePath which I specify. Any way to override this behaviour inside XAML? Or is there an easier way to achieve this?
EDIT:
I've since edited the code posted above to be:
<ComboBox ItemsSource="{
Binding RelativeSource={
RelativeSource AncestorType=UserControl
},
Path=DataContext.DivingTypes
}"
SelectedValue="{
Binding Dives[0].divingType, Mode=TwoWay
}"
/>
This makes the correct value show in the combobox at the start, DivingType.ID is loaded from the Diver.Dives[0].divingType, but it still does not set the property when I select a new value in the dropdown box.
Use UpdateSourceTrigger=PropertyChanged.
Explanation here.
Have you tried to implemented INotifyPropertyChanged in your viewmodel and then raise the PropertyChanged event when the SelectedValue gets set.
If this is not working, can you set the SelectedValue
SelectedValue="{Binding Path=divingType, Mode=TwoWay}"

Name checkboxes in ListView

I have a ListView control that contains one column with checkboxes only. Is it possible to give those checkboxes some names (indexes - like 1, 2, 3...)?
I need it because I want to identify a concrete checkbox in ToggleButton_OnCheckedUnchecked event in some way.
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Value, Mode=OneWay}" IsThreeState="False"
Checked="ToggleButton_OnCheckedUnchecked"
Unchecked="ToggleButton_OnCheckedUnchecked"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
Don't do it that way. Instead, have each line in your ListView have an IsChecked property, and change the IsChecked Binding to TwoWay. That way, you don't need to use the Checked and UnChecked events at all.
public class LineViewModel
{
public bool IsChecked
{
get { return _isChecked;
}
set
{
// do something here
}
}
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" IsThreeState="False"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
For best results, implement INotifyPropertyChanged too.
Probably dynamic binding some names can be done through the Binding, like that:
<CheckBox Name="{Binding IdCheckBox}" ... />
Quote from the MSDN:
Data binding a Name is technically possible, but is an extremely uncommon scenario because a data-bound Name cannot serve the main intended purpose of the property: to provide an identifier connection point for code-behind.
In short, the Binding Name property for control is impossible and undesirable. But you can use the attached dependency property, like this:
<CheckBox local:GiveName.Name="{Binding Template_Name1}" ... />
In any case, this is not the solution to your problems.

Categories

Resources