Bind DataGrid Column to ViewModel Property - silverlight

If I have the following DataGrid, how would I go about binding the Visibility of the TemplateColumn to a property on my ViewModel? The code I have here is based on a recommendation from this SO Question but no luck.
<sdk:DataGrid Visibility="{Binding GridVisible}" DataContext="{Binding}" Grid.Row="1" ItemsSource="{Binding Path=BookSource}" x:Name="bookGrid" AutoGenerateColumns="False" IsReadOnly="True">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Visibility="{Binding Path=DataContext.GridImgColumnVisible, ElementName=bookGrid}">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Stretch="Fill" Source="{Binding Path=SmallImgURI}"></Image>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTextColumn Header="Title" Width="*" Binding="{Binding CurrentBook.Title}" />
<sdk:DataGridTextColumn Header="Published" Width="150" Binding="{Binding CurrentBook.Published, StringFormat=d}" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
EDIT - I've also tried:
<sdk:DataGridTemplateColumn Visibility="{Binding Path=DataContext.GridImgColumnVisible, ElementName=root}">
and I've also added a button to my actual control, and bound its visibility to this property without difficulty.

It can be achieved in WPF, but Silverlight DataGrid is different. Visibility property isn't dependency property (you can't perform binding), Columns aren't belong to VisualTree and don't inherit DataContext.
Use code-behind, something like this:
var model = (MyViewModel)this.DataContext;
model.PropertyChanged += (s,e) =>
{
if(e.PropertyName == "GridImgColumnVisible")
this.UpdateGridColumnVisibility(model.GridImgColumnVisible);
};
public void UpdateGridColumnVisibility(Visibility imageVisibility)
{
var imgColumn = bookGrid.Columns.Cast<DataGridColumn>().FirstOrDefault(c => ((string)c.GetValue(Panel.NameProperty)) == "imgColumn");
if(imgColumn != null)
imgColumn.Visibility = imageVisibility;
}
And add the name to the column:
<sdk:DataGridTemplateColumn x:Name="imgColumn">

Related

Binding command parameter of data grid context menu to selected row's column value

I have a datagrid being binded to an ObservableCollection. The data grid has a context menu. When context menu item is clicked, the binded command gets triggered, but I would like to pass command parameter that is binded to the Id of the data grid row that is selected. The command gets triggered but the parameter is null.
Below is the code that I have tried.
<DataGrid Name="users" ItemsSource="{Binding UsersModel}" CanUserAddRows="False" IsReadOnly="True" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="User Id" Width="auto"/>
<DataGridTextColumn Binding="{Binding Name}" Width="*" Header="User Name"/>
<DataGridTextColumn Binding="{Binding IsRegistered, Converter={StaticResource BoolToYesNoConverter}}" Width="auto" Header="Registered" />
<DataGridTextColumn Binding="{Binding RegisteredOn}" Width="*" Header="Registration Date"/>
</DataGrid.Columns>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Modify" Command="{Binding Modify}" CommandParameter="{Binding Id}"/>
<MenuItem Header="Delete" Command="{Binding Delete}" CommandParameter="{Binding Id}" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
I am expecting the Id of the selected row be passed as the command parameter.
I would create a SelectedRow property on your viewmodel to store the selected row in the grid, then bind the context menu item command to a command on the viewmodel that references the SelectedRow property.
I had to use the PlacementTarget on the context menu relative source.
// in viewmodel
public DelegateCommand ModifyCommand { get; }
ModifyCommand = new DelegateCommand(() => { var Id = SelectedRow.Id; //... });
private UsersModel _selectedRow;
public UsersModel SelectedRow
{
get => _selectedRow;
set
{
_selectedRow = value;
OnPropertyChanged(nameof(SelectedRow));
}
}
// in view
DataGrid Name="users" ItemsSource="{Binding Items}" CanUserAddRows="False" IsReadOnly="True" AutoGenerateColumns="False"
SelectedItem="{Binding SelectedRow, Mode=TwoWay}" >
// and on context menu
<MenuItem Header="Modify" Command="{Binding PlacementTarget.DataContext.ModifyCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" />
Add a SelectedId property to your view model (where the UsersModel and the command properties are defined) and bind the DataGrid's SelectedValue property to that property.
Then use SelectedId for the CommandParameter Binding. Of course make sure that SelectedId fires the PropertyChanged event.
<DataGrid ... SelectedValuePath="Id" SelectedValue="{Binding SelectedId}">
...
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Modify"
Command="{Binding Modify}"
CommandParameter="{Binding SelectedId}"/>
<MenuItem Header="Delete"
Command="{Binding Delete}"
CommandParameter="{Binding SelectedId}" />
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
I asuume that your ViewModel class implemented INotifyPropertyChanged interface and I assume that you implement a method for trigger property changes, here I called this method RaisePropertyChanged. After all of that,
define a private field selectedUsers and a property with name of SelectedUsers in your viewModel Class:
private UsersModel selectedUsers;
public UsersModel SelectedUsers
{
get => _selectedFields;
set
{
_selectedFields = value;
Modify.RaiseCanExecuteChanged();
Delete.RaiseCanExecuteChanged();
}
}
After that you add it, you must add SelectionChanged event to your Grid, and then in code-behind set-up value for your seletedUser as below:
private void usersGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
usersViewModel.SelectedUsers = usersGrid.SelectedItem as UsersModel;
}
And bind selectedItem of DataGrid to selectedUser:
<DataGrid Name="users" ItemsSource="{Binding Items}" CanUserAddRows="False"
IsReadOnly="True" AutoGenerateColumns="False"
SelectedItem="{Binding SelectedUser}" >
When you do these work, selectedUser is accessable in your viewModel.

Pass values to ValueConverter from two different binding sources in single DataGrid

What I want to do is to color DataGridRow red if validation fails. I attempted to do so with ValueConverter but I have no idea how to pass value from more than one binding source.
I've got datagrid with binding to collection of objects. Then there's a
DataGridTemplateColumn with ComboBox and Textbox inside (only one is visible at time), and the ComboBox has non related to bound collection binding source.
See code:
<DataGrid Grid.Row="4" Grid.Column="0" x:Name="DGR_Slots" Grid.ColumnSpan="5" Grid.RowSpan="3" ItemsSource="{Binding ElementName=WND_ItemCraftingWindow, Path=SelectedSchematic.Slots}" AutoGenerateColumns="False" ColumnWidth="*">
<DataGrid.Resources>
<classes:ValidationConverter x:Key="ValidationConverter" />
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding Converter={StaticResource ValidationConverter}}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" Visibility="Collapsed"/>
<DataGridTextColumn Header="Slot Type" Binding="{Binding SlotType}" />
<DataGridTextColumn Header="Mat Type" Binding="{Binding MaterialType}" />
<DataGridTextColumn Header="Count" Binding="{Binding MaterialCount}" />
<!--<DataGridComboBoxColumn Header="Material" />-->
<DataGridTemplateColumn Header="Material">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Visibility="Visible" MouseDoubleClick="MaterialCell_MouseDoubleClick" ItemsSource="{Binding ElementName=WND_ItemCraftingWindow, Path=Materials}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectionChanged="MaterialComboBox_SelectionChanged"/>
<TextBox Visibility="Collapsed" MouseDoubleClick="MaterialCell_MouseDoubleClick" PreviewKeyUp="MaterialCell_TextBox_PreviewKeyUp" KeyUp="MaterialCell_TextBox_KeyUp" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
WND_ItemCraftingWindow is the Window that owns the datagrid and the Materials is ObservableCollection property inside that window.
I need to pass the object from DataGrid.ItemsSource collection (that works now) and SelectedItem from Combobox in same row (i dont know how to do that) into ValidationConverter.
Validation of the row can be performed only with the object from DataGridRow and SelectedItem from combobox.
Also I'm open to different solutions to color the row after such validation.
Alright I managed to do it hackish way.
Datagrid code (removed meaningless parts):
<DataGrid x:Name="DGR_Slots" ItemsSource="{Binding ElementName=WND_ItemCraftingWindow, Path=SelectedSchematic.Slots}" AutoGenerateColumns="False" ColumnWidth="*" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Material">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Visibility="Visible" ItemsSource="{Binding ElementName=WND_ItemCraftingWindow, Path=Materials}" SelectionChanged="MaterialComboBox_SelectionChanged" />
<TextBox Visibility="Collapsed" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
SelectionChanged event
private void MaterialComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// ...
var slot = (DGR_Slots.SelectedItem as SchematicSlot);
var mat = ((sender as ComboBox).SelectedItem as Material);
var row = WPFHelper.GetRow(DGR_Slots, DGR_Slots.SelectedIndex);
row.Background = new SolidColorBrush(slot.MaterialType != mat.MaterialType ? Colors.Red : Colors.White);
}
WPFHelper GetRow method (found somewhere on the internet)
static public DataGridRow GetRow(DataGrid dg, int index)
{
DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// may be virtualized, bring into view and try again
dg.ScrollIntoView(dg.Items[index]);
row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
Although I'm still open to other solutions.

Bind combobox to collection in viewmodel

I have a combo box inside the datagrid. Datagrid is bound to an item source. I want to fill the combo box to a collection apart from its parent collection but its not working.
<data:DataGrid x:Name="dgTransferStockroomGLDetails" AutoGenerateColumns="False" ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}"
VerticalScrollBarVisibility="Visible" ItemsSource="{Binding StockroomTransferDetails, Mode=TwoWay}"
CanUserResizeColumns="False" VerticalAlignment="Top" RowBackground="White" AlternatingRowBackground="White" GridLinesVisibility="All" Height="400">
<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header="From Stockroom" Width="200" CanUserReorder="True" CanUserSort="True" IsReadOnly="False">
<data:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="prim:DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</data:DataGridTemplateColumn.HeaderStyle>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!--<TextBox Text="{Binding From_Stkrm_Id}" Width="200" Height="30" />-->
<ComboBox Width="200" Height="30" ItemsSource="{Binding WiingsStkrmList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
Here is my view model
private ObservableCollection<BuyerWebService.Stockroom> wiingsStkrmList;
public ObservableCollection<BuyerWebService.Stockroom> WiingsStkrmList
{
get
{
return wiingsStkrmList;
}
set
{
wiingsStkrmList = value;
SendChangedNotification("WiingsStkrmList");
}
}
private ObservableCollection<BuyerWebService.StockroomTransfer> stockroomTransferdetails;
public ObservableCollection<BuyerWebService.StockroomTransfer> StockroomTransferDetails
{
get
{
return stockroomTransferdetails;
}
set
{
stockroomTransferdetails = value;
SendChangedNotification("StockroomTransferDetails");
}
}
I checked the output window, it says
'WiingsStkrmList' property not found on 'BuyerPortal.BuyerWebService.StockroomTransfer'
StockroomTransfer is used to bind the datagrid. The combo is trying to find the WiingsStkrmList in parent item source.
How to bind it to a different collection?
You need to specify a relative source for where the bound property can be found, usually by pointing at a parent element that is bound to the view model. Something like this:
ItemsSource="{Binding StockroomTransferDetails, RelativeSource={RelativeSource AncestorType=Grid}}"
Change AncestorType=Grid to something that has a datacontext of your viewmodel ... something outside of your datagrid basically.

DataGridColumn dynamic(parametric) binding to a column of a collection

I'm trying to bind a datagrid to a collection, but I do not know which are the columns of the collection.
So I try to to bind the column to a property that is passed to the view.
my code is:
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding ElementName=_VMItemSelector}"/>
<WPFCtrlDg:SelfBindingDataGrid x:Name="_sbdgAvailableItems" ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Path=Availables}"
SelectedItem="{Binding Path=CurrentAvailableItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="{Binding DataContext.IsReadOnly,
Source={StaticResource ProxyElement},
Mode=OneWay}">
<WPFCtrlDg:SelfBindingDataGrid.Columns>
<WPFCtrlDg:ExtDataGridTextColumn Header="{Binding DataContext.DetailBinding,
Source={StaticResource ProxyElement},
Mode=OneWay}"
Tag="CD_FUNCTION"
Visibility="Hidden"/>
<WPFCtrlDg:ExtDataGridTextColumn Header="{Binding DataContext.RightColumnHeader,
Source={StaticResource ProxyElement},
Mode=OneWay}"
Binding="{Binding DataContext.RightColumnHeader,
Source={StaticResource ProxyElement},
Mode=OneWay}"
Width="*"/>
and in the view I have
private string _rightColumnHeader;
public string RightColumnHeader
{
get { return _rightColumnHeader; }
set { _rightColumnHeader = value; }
}
the value is set to "DS_FUNCTION" which is a filed of the collection I bind, but what I see in the dataGrid is alwats "DS_FUNTION" for each element of the collection.

How to find sibling of a control that raised an event in DataGrid WPF

I have a DataGrid whole columns are given below.
<my:DataGrid.Columns>
<my:DataGridTemplateColumn Header="Last Name" MinWidth="160" SortMemberPath="[LAST_NAME]">
<my:DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<TextBlock Name="lblLastName" Padding="5"
Text="{Binding [LAST_NAME]}" />
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
<my:DataGridTemplateColumn Header="New Age Group" IsReadOnly="True"
MinWidth="130">
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedValue="{Binding AgeGroupId}"
DisplayMemberPath="AgeGroupName" ItemsSource="{Binding}"
Name="ddlNewAgeGroup" Loaded="ddlNewAgeGroup_Loaded"/>
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
<my:DataGridTemplateColumn Header="Update" MinWidth="75" Width="100">
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Update" Name="btnUpdate" Click="btnUpdate_Click"/>
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
</my:DataGrid.Columns>
On btnUpdate_Click event I want to get the value that has been set on ddlNewAgeGroup
But I dont know how to find this Combobox on Button Click Event in WPF. I am using DataTables to Bind grids.
(e.OriginalSource as Button).DataContext should be the object binded to your row, so it should have AgeGroupId property (as seen from ComboBox.SelectedValue), and it's a collection (as seen from ComboBox.ItemsSource). So you may find use AgeGroupName this way:
private void Grid_Click(object sender, RoutedEventArgs e) {
var row = (e.OriginalSource as Button).DataContext as %YourDataType%;
var agegroupname = row.First(item => item.AgeGroupId == row.AgeGroupId).AgeGroupName;
// TODO: do what you need with "agegroupname".
}
PS. Please set ComboBox.SelectedValuePath to something like AgeGroupId: without it, ComboBox.SelectedValue is equal to a whole record, not just Id

Resources