WPF Xaml GridView SelectionChanged Behavior - wpf

I am working with a WPF view with Prism.MVVM which allows our users to edit records.
Originally the record to be edited was selected via ComboBox.
<ComboBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Records}"
SelectedItem="{Binding SelectedRecord}"/>
This worked, but users wanted a more efficient way of finding which records had fields which needed updating so we have added a read only DataGrid which they can sort and visually spot which records they are interested in. Next they want to select the record to edit off the grid (but keep the combo box). This is where things go wrong.
Ideally the behavior we are looking for is:
If user selects a record from combo box:
The selected record is loaded in the form
The selected record is shown as selected in the combo box.
The selected record is shown as selected in the grid.
If user selects a record in Grid
single click to select record.
The selected record is loaded in the form
The selected record is shown as selected in the combo box
The selected record is shown as selected in the grid.
Most Successful Attempt
Trigger Command on SelectionChanged event of DataGrid
<DataGrid x:Name="TheDataGrid"
ItemsSource="{Binding Source={StaticResource GridRecords}}"
SelectedItem="DataContext.SelectedRecord, ElementName=LayoutRoot, Mode=OneWay}">
...
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction CommandParameter="{Binding SelectedItem, ElementName=TheDataGrid}"
Command="{Binding DataContext.SelectRecordFromGridCommand, ElementName=LayoutRoot}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
DelegateCommand:
Public ReadOnly Property SelectRecordFromGridCommand As DelegateCommand(Of TheRecordType) = new DelegateCommand(Of TheRecordType)(Sub(r) SelectedRecord = r)
This was attempted with various options for the SelectedItem binding mode.
If the DataGrid SelectedItem binding is removed, We get 1,2,4,5,6, and 7. but selecting the record from the combo box would not show the record as selected in the grid.
If the DataGrid SelectedItem binding is set to OneWay, Selecting a record via the combo box breaks: Setting SelectedRecord triggers the SelectionChanged event in the DataGrid, which uses the value before the event and effectively sets everything back to the original value.
This can be remedied by introducing a sentinal on the Set of the Property in the ViewModel
Private _selectedRecord As TheRecordType
Private _enableRecordSelection As Boolean = true
Public Property SelectedRecord As TheRecordType
Get
Return _selectedRecord
End Get
Set(value As TheRecordType)
If _enableRecordSelection
_enableRecordSelection = false
SetProperty(_selectedRecord , value)
_enableRecordSelection = true
End If
End Set
End Property
This actually works, and we came up with it while writing the question, but feels horribly hacky. My gut is telling me there has to be a better way so I'm still asking:
Is there a clean (preferably xaml only) way to set this up?
The other most successful things we tried:
Straight xaml configuration for the DataGrid with TwoWay binding
<DataGrid x:Name="TheDataGrid"
ItemsSource="{Binding Source={StaticResource GridRecords}}"
SelectedItem="DataContext.SelectedRecord, ElementName=LayoutRoot, Mode=TwoWay}"/>
With this, we satisfy requirements 1 through 6; however when selecting the record through the grid, the previous record is always highlighted instead of the current one.
DataGrid.InputBindings
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftClick"
CommandParameter="{Binding SelectedItem, ElementName=TheDataGrid}"
Command="{Binding DataContext.SelectRecordFromGridCommand, ElementName=LayoutRoot}"/>
</DataGrid.InputBindings>
With no SelectedItem binding, this behaves similarly to the no binding InteractionTrigger on SelectionChanged, except it requires the user to perform multiple mouse actions. A first click selects the row in the grid (actual bold blue selection) The second click triggers the Command.
With a OneWay binding on SelectedItem, this behaves similarly to the straight xaml config, again except needing to click multiple times.
Again to reiterate the question:
Is there a cleaner way to accomplish the 7 requirements than to resort to the sentinal value on the property setter?

According to you ask, I understand that you want to sync the selected item in Datagrid and ComboBox. If I were you, I will use the that two control binding the same object(SelectedRecord). I only familiar with C#, so the code is write in C#. Hope it can help you.
For XAML:
<DataGrid ItemsSource="{Binding Records}"
SelectedValue="{Binding SelectedRecord}" />
<ComboBox Grid.Column="1" ItemsSource="{Binding Records}"
DisplayMemberPath="Id"
SelectedValue="{Binding SelectedRecord}" />
For ViewModel:
public ObservableCollection<Record> Records { get; } = new ObservableCollection<Record>();
public Record SelectedRecord
{
get { return _selectedRecord; }
set
{
_selectedRecord = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class Record
{
private static int id = 0;
public Record()
{
Id = ++id;
}
public int Id { get; set; }
}

Related

how to get muliplte checked datagrid checkbox items with MVVM

I have datagrid which consist of muliple data grid checkboxes,i want to get the datagrid checked items,i am able to get only the single selected row item,but i need collection of checked checkboxes, below code i am using .Please let me know how to resolve this
**Xaml*****
<DataGrid SelectedItem="{Binding SelectedRow, Mode=TwoWay}" ItemsSource="{Binding ManualDataTable}" Background="{Binding ElementName=gd,Path=Background}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding UserID}" Width="60" />
<DataGridTextColumn Binding="{Binding Name}" Width="140" Header="Name" FontSize="16" FontFamily="segoe_uilight" IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
<Button BorderBrush="{x:Null}" Content="Add participants" Width="220" Height="50" FontSize="20" Command="{Binding SaveAssignedUser}"/>
*****View Model***********
DataTable _manualDataTable;
public DataTable ManualDataTable
{
get
{
return _manualDataTable;
}
set
{
_manualDataTable = value;
RaisePropertyChanged("ManualDataTable");
}
}
private List<DataRowView> selectedRow;
public List<DataRowView> SelectedRow
{
get
{
return selectedRow;
}
set
{
selectedRow = value;
RaisePropertyChanged(() => SelectedRow);
}
}
public void ExecuteSaveAssignedUser()
{
SelectedRow = new List<DataRowView>();**///need multiple checked checkboxes collection**
foreach (DataRowView drv in SelectedRow)
{
}
}
Your checkbox column is bound to a a property called UserID, which I guess is the name of a column in your DataTable. Whenever you check/uncheck a checkbox in the datagrid, the binding will change the value of UserID to true or false, in the relevant DataRow of the DataTable.
Why would you bind a checkbox column to a user ID? I suspect this isn't what you actually want. Instead you probably need to add a boolean column to your DataTable (e.g. IsSelected), and bind your checkbox column to that instead.
It looks like you are using MVVM, so it isn't possible to access the datagrid items from within your view-model. You can only access the data that the grid is bound to (i.e. your DataTable). Your view-model code needs to iterate through the rows in this DataTable, examining the value of the UserID column (or the "IsSelected" column if you add one!) to determine whether that row's checkbox is checked in the datagrid.
The SelectedItem property that you are binding to is completely unrelated to your checkbox column. Checking and unchecking these will have no effect on SelectedItem. This property is used to determine which row the user has selected with the mouse (which gives the row a different b/g colour). Forget about this property - it's not relevant to what you are trying to do.
As already mentioned elsewhere, try and avoid DataTables in WPF. Instead, define some kind of "User" class, and have your view-model expose a collection of these for your grid to bind to.
You seem to be missing the entire point of data binding... that is that you have access to all of the data that is displayed in your DataGrid from your code behind. You have bound the ManualDataTable DataTable property to the DataGrid, so the values that are data bound to the RadioButton controls are all in one column of your DataTable.
Therefore, all you need to do to access them is to look in your DataTable. There are several ways to achieve this, but here is one:
foreach(DataRow row in ManualDataTable.Rows)
{
if (row[requiredColumnIndex] == true) AddRowToSomeCollection(row);
}
However, if you're going to continue to use WPF, I'd seriously advise that you stop using these old classes, such as DataTables. Generally in WPF, we define custom classes and that makes everything much simpler in the long run.

How can I perform a custom action on the data of a DataGridRow when it is selected?

I've been trying to figure out how to get this custom behaviour into a datagrid with out having much look when searching online for solutions.
Given the following datagrid (some xaml removed for brevity):
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="auto">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have the checkbox successfully bound to the databound object for each row. (Note: I'm using a DataGridTemplateColumn rather than DataGridCheckBoxColumn so that you do not need to double-click to change the value).
What I would like to achieve is to have the ability to tick the checkbox / update the Selected property of the databound object when the user selects a row. Effectively making the entire row click set the checked property of the checkbox. Ideally, I'd like to do this without a code behind file if possible as I'm trying to keep my code behinds as clean as possible.
Another feature which I would like, if possible, would be that clicking on a row would toggle it's selected property so that if you click on another one, the previous one stays selected as well as the new one.
Any help is much appreciated.
For clarity. I understood
Another feature which I would like, if possible, would be that
clicking on a row would toggle it's selected property so that if you
click on another one, the previous one stays selected as well as the
new one.
in the way, that you want the CheckBox of the an item, respectively the Selected property on the items ViewModel, to stay selected, when the next DataGridRow is selected, but not the DataGridRow itself? Is that correct?
My suggestion is to extend the behavior of your DataGrid using *WPF behavior*s (This is a good introduction. This way you can keep your codebehind clear, but don't have to twist XAML to make it do what you want.
This is basically the idea of behaviors: Writing testable code, which is not coupled to your concrete view, but nonetheless allowing you to write complicated stuff in 'real' code and not in XAML. In my opinion your case is a typical task for behaviors.
Your behavior could look about as simple as this.
public class CustomSelectionBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
// Set mode to single to be able to handle the cklicked item alone
AssociatedObject.SelectionMode = DataGridSelectionMode.Single;
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
// Get DataContext of selected row
var item = args.AddedItems.OfType<ItemViewModel>();
// Toggle Selected property
item.Selected = !item.Selected;
}
}
Attaching the behavior to your specific DataGrid, is done in XAML:
<DataGrid ...>
<i:Interaction.Behaviors>
<b:CustomSelectionBehavior />
</i:Interaction.Behaviors>
...
</DataGrid>
You need to reference
System.Windows.Interactivity.dll
which contains the Behavior<T> baseclass as well.

Binding Visibility to Yes/No ComboBox with Converters

I've got a form that gets given a datarow from a dataset to bind all its elements. One of them is a bool, but I want that bool to be represented by by a Yes/No combo box. So I did this and it works nicely.
I also want to bind the visibility of a couple elements to this bool field. When the form loads, the initial setting of the visibility works. When I change the combobox selection, the ConvertBack() method of the ComboBox gets called (i.e. it's setting the bound value). But the other elements that have their visibility bound to that same field don't get updated. I set breakpoints in the Conversion methods and they never get called like they do when the form loads.
Here's the relevant XAML:
<ComboBox SelectedIndex="{Binding Path=[Adequate], Converter={StaticResource b2iConverter}}" Name="cb_Adequate" >
<ComboBoxItem>Yes</ComboBoxItem>
<ComboBoxItem>No</ComboBoxItem>
</ComboBox>
<Label Content="Reason:"
VerticalAlignment="Center"
Visibility="{Binding Path=[Adequate],
Converter={StaticResource b2vConverterInverse}}"/>
<TextBox Text="{Binding Path=[NotAdequateReason]}"
Visibility="{Binding Path=[Adequate],
Converter={StaticResource b2vConverterInverse}}"/>
"Adequate" is the bool field
b2iConverter is just booleanToIndexConverter (from the above link)
b2vConverterInverse is just an inverted boolean to visibility converter (I want the label and textbox shown when Adequate is FALSE or 0).
Thanks for any help. I can post more code if needed, I figure the problem is in the XAML...
EDIT: Apparently it's not possible with XAML (see Greg's post below), so I just do it in code:
private void cb_Adequate_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Visibility vis = (cb_Adequate.SelectedItem as ComboBoxItem).Content.ToString() == "Yes" ? Visibility.Collapsed : Visibility.Visible;
label_Reason.Visibility = tb_AdequateDesc.Visibility = vis;
}
If you want your UI elements to change state when a data property changes, you need to implement INotifyPropertyChanged on your data class.
This means that you can't use the DataRow for your purposes. You'll have to create a new class, then at run time populate it with values from the DataRow and then bind that object to your view.

How to Aceess cell level ComboBox in WPF DataGrid?

My data grid column template which has combo box in it is as below .
<my:DataGridTemplateColumn x:Name="dgColReferece" Header="References" >
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbReferece_SRV" Loaded="cmbReferece_SRV_Loaded" Width="160" SelectionChanged="cmbReferece_SRV_SelectionChanged"
IsTextSearchEnabled="True" SelectedValue="{Binding Reference, Mode=TwoWay}" >
</ComboBox>
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
</my:DataGridTemplateColumn>
All combo boxes have Add new as one item in them which has value -2. When the user clicks on add new a new item added to the database and should be rebound to all comboboxes in the grid.
Below is my code behind for SelectionChanged
private void cmbReferece_SRV_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ ComboBox objComboBox = (ComboBox)sender;
if (objComboBox.SelectedValue.ToString() == "<-- Add New -->")
{
//code for saving new item entered by user to database
if (IsSaved)
{
DataSet dsReference = (DataSet)GetFStdReference();
CommonCalls.BindDropDownList(cmbReferece_SRV, dsReference.Tables[0], "Reference", "Reference");
}
objComboBox.SelectedValue = -1;
}
}
This will bind the new item only to the combobox in the selected row. But I need it to bind to all comboboxes? How Can I do this. I am new to wpf and binding stuffs > How can i Proceed ?
You code above is a little confusing. Can you explain more what are you trying to do. I can see several deviations from the proper WPF programming practises esp. regarding using comboboxes in datagrid.
E.g.
Why are you using events like cmbReferece_SRV_SelectionChanged and not using SelectedValue and SelectedValuePath via Converter?
Also what is your ComboBox.ItemsSource? DataTable? List of objects?
Why are you setting ItemsSource of a ComboBox in its own SelectionChanged event, which is counterproductive.
I understand that you are new to WPF, so may be if you explain your problem to me, I can suggest some useful WPF practises of coding for your issue.

How to bind an observable collection to Multiple user controls at runtime?

I am stucked at the part where I have to bind a collection to a dynamic usercontrol. Scenario is something like this.
I have a dynamic control, having a expander , datagrid, combobox and textbox, where combox and textbox are inside datagrid. There are already two collections with them. One is binded with combobox and another is binded with datagrid. When the item is changes in combox its respective value is set to its respective textbox, and so on. and this pair of value is then set to the collection binded with datagrid. A user can add multiple items.
Now the main problem is that all these things are happening inside a user control which is added dynamically, that is on button click event. A user can add desired numbers of user controls to the form.
problem is coming in this situtaion. Say I have added 3 controls. Now in 1st one if i add a code to the collection then it gets reflected in the next two controls too, as they are binded with same collection.
So, I want to know is there anyway to regenrate/rename the same collection so that the above condition should not arise.
It's hard to answer your question without seeing the bigger picture, however I have a feeling you are going about this the wrong way. It appears that you are adding instances of your user control directly from code. Instead of doing that, you should create some kind of ItemsControl in your XAML, and in its ItemTemplate have your user control. Bind that ItemsControl to a collection in your view model, and only manipulate that collection.
You should not be referring to visual controls in your view model or code behind. Whenever you find yourself referencing visual elements directly from code, it should raise a warning flag in your mind "Hey! There's a better way than that!"...
Example:
The view model:
public class ViewModel
{
public ObservableCollection<MyDataObject> MyDataObjects { get; set; }
public ViewModel()
{
MyDataObjects = new ObservableCollection<MyDataObject>
{
new MyDataObject { Name="Name1", Value="Value1" },
new MyDataObject { Name="Name2", Value="Value2" }
};
}
}
public class MyDataObject
{
public string Name { get; set; }
public string Value { get; set; }
}
The window XAML fragment containing the list box and the data template:
<Window.Resources>
...
<DataTemplate x:Key="MyDataTemplate">
<local:MyUserControl/>
</DataTemplate>
</Window.Resources>
...
<ListBox ItemsSource="{Binding MyDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}"
HorizontalContentAlignment="Stretch"/>
The user control:
<UniformGrid Rows="1">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Value}" HorizontalAlignment="Right"/>
</UniformGrid>

Resources