I'm looking for a way to select multiple checkboxes at once in WPF. I'm using the MVVM pattern without any further augments like PRISM. I'm loading data from a MySQL database and binding it to a Data Grid. Then I want to select some of those tables and then add them to another Data Grid. The solution I came up with was creating checkboxes dynamically and binding them to a IsSelected property in my data grid.
<DataGridTemplateColumn
Header=""
Width="auto"
CanUserResize="False"
CanUserReorder="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="RadioButtonDatabase"
IsChecked="{Binding IsSelected,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The good thing: It works. The bad thing: Each entry in my Datagrid has to be manually clicked, so that IsSelected is updated for each object. I'd like to see a solution that makes multiselection possible (with shift), and possibly a key(space?). Other good ideas are also welcome. Research on the internet didn't yield a satisfying solution.
I'd prefer no answers using codebehind, I'm trying to stick as close to strict MVVM as possible.
You can define Command for your checkbox in your ViewModel(cause I hope you also want binding to reflect on your IsSelected property for each data item when checked):
<CheckBox Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGrid}},Path=DataContext.CheckedCommand}" .../>
ViewModel:
{
CheckedCommand = new RelayCommand(() => this.CheckAllCheckboxes());
}
public RelayCommand CheckedCommand { get; set; }
public void CheckAllCheckboxes()
{
//set IsSelected true for all items here
}
Get Relay Command from here
Update:
Define a Row style for datagrid that will bind the IsSelected Property of DataGridRow to your Model's any property and then in command action check if row is selected:
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsRowSelected}" />
</Style>
</DataGrid.RowStyle>
If also want to enable/disable on selection add this binding to checkbox:
IsEnabled="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}},Path=IsSelected,Mode=OneWay}"
Related
I am new to WPF, trying to fix up an autosuggest DataGridComboBoxColumn
<DataGridComboBoxColumn x:Name="list_itemname" Width="*" Header="Item Name" >
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsEditable" Value="True"/>
<Setter Property="ItemsSource" Value="{Binding Path=itemlist}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
You have to catch event related to changing the cell: CellEditEnding. First you have to declare method that will handle the event:
void cellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
var editedComboBoxColumn = e.EditingElement as ComboBox;
}
and then hook up handler with your grid:
<DataGrid x:Name="grid" CellEditEnding="cellEditEnding" />
Instead of using a datagrid combobox column, I recommend you use a datagrid template column.
Roughly
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox IsEditable="True"
Text="{Binding PropertyInRow}"
ItemsSource="{Binding itemlist}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Here PropertyInRow is the name of a propfull in a row viewmodel. Whatever your itemssource of the datagrid is bound to being an observablecollection.
If you want to do something when the text changes then you can put code in the setter of that propfull.
This is the mvvm pattern and you should learn that from the start if you plan on doing any serious wpf development. Certainly if you plan on working in a commercial wpf team because they all use mvvm.
Also.
If itemlist is not a property in the rowviewmodel and instead is somewhere in the window viewmodel then you'll want some relativesource on that binding.
If your ItemSource is a ListCollectView, you can get the current item like this:
public void OnCellValueChanged(object sender, CellValueChangedEventArgs e)
{
var currentItem = itemlist.CurrentItem; // to get the whole current item
// Or you just get the current changed cell value from the EventArgs:
var currentValue = e.Value?.ToString();
}
I've been spending more then half a day to get this task sorted and for a tree I cannot see the forest.
The aim is to display data (DataGrid) as several grids in sequence with dynamic count of columns, where each cell (not just column) can or cannot be editable with two way binding.
I would like to avoid using code behind approach and I believe xaml can offer me what I need. Other thing is mvvm injection.
Lets make it simple and do binding for one table first.
My first tough was to create DataTable but this on cannot be used for cell editable level. Then I created collection of collections of objects (for one table -> many rows -> many columns).
public class DataGridCell : BaseViewModel
{
public string Value
....
public bool IsEditable
....
}
Then I have another VM which represents one table (grid) which contains VM upon
public class DataGridItem : BaseViewModel
{
public string TableName
{
....
}
public ObservableCollection<ObservableCollection<DataGridCell>> Data
{
....
}
}
Then my xaml looks like this
<DataGrid ItemsSource="{Binding Path=Data}" AutoGenerateColumns="True">
<DataGrid.Resources>
<DataTemplate x:Key="ReadonlyCellTemplate">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
<DataTemplate x:Key="EditableCellTemplate">
<TextBox Text="{Binding Value}" />
</DataTemplate>
</DataGrid.Resources>
<DataGridTemplateColumn CellTemplate="{StaticResource ReadonlyCellTemplate}">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ContentPresenter x:Name="Presenter" ContentTemplate="{StaticResource ReadonlyCellTemplate}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEditable, PresentationTraceSources.TraceLevel=High}" Value="True">
<Setter TargetName="Presenter" Property="ContentTemplate" Value="{StaticResource EditableCellTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid>
The idea is to dynamically choose which cell will be filled in by data input controls and which not.
Problem is binding. I cannot figure out how to bind concrete cell element in the collection.
Thanks for any possible advice
I hope it's not too late but this is how I solved my issues with binding cells to dynamic data:
Problems binding to a the content of a WPF DataGridCell in XAML
(With code added as requested)
I am having issue binding text decoration to datagrid column DataTemplate. Here is the xaml setup.
<UserControl x:Class="myClass"...>
<DataGrid
Name="_dataGrid"
FontFamily="{Binding Font_Family}"
FontWeight="{Binding Font_Weight}">
<Controls:DataGrid.Columns>
<Controls:DataGridTemplateColumn>
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Data1}"
Foreground="Red"
//This binding does not work
TextDecorations="{Binding Decor_Underline"/>
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
</Controls:DataGridTemplateColumn>
</Controls:DataGrid.Columns>
</DataGrid>
</UserControl>
The error i get goes something like this.
BindingExpression with XPath cannot bind to non-XML object.; XPath='Model.Decor_Underline' BindingExpression:Path=Decor_Underline; DataItem='DataGrid'
If i manually set the text decoration it works fine. TextDecoration = "Underline"
The bindings for Font_Family and Font_Weight is working fine.
I have tried doing the following; But since DataGrid does not have TextDecorations property it did not help.
TextDecorations="{Binding Decor_Underline ,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}
Here is the property in the model. The event is raised as expected without no effect on the control - its as if no one is listening. The scenario is i press a button and the grid columns textbox gets underlined.
public TextDecorationCollection Decor_Underline {
get { return decor_Underline; }
set {
if(someFlg)
decor_Underline = TextDecorations.Underline;
RaisePropertyChanged("Decor_Underline");
}
}
TextDecorationCollection decor_Underline;
Any Ideas?
I wonder why you want to create your own DataGridTemplateColumn when it contains just a TextBlock ? I have little experience with DataGridTemplateColumn, but why not use a DataGridTextColumn ? Here is a solution that works with DataGridTextColumn and maybe you can do it similarly for DataGridTemplateColumn ?
<Window.Resources>
<Style x:Key="TextDecorationsStyle" TargetType="TextBlock">
...
<Setter Property="TextDecorations"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}},
Path =Item.Decor_Underline}" />
</Style>
</Window.Resources>
...
<DataGrid ...>
...
<DataGrid.Columns>
...
<DataGridTextColumn Binding="{Binding Path=Data1}"
ElementStyle="{StaticResource TextDecorationsStyle}"/>
</DataGrid.Columns>
For the complete code and in detail explanation how it works see my article on CodeProject: Guide to WPF DataGrid Formatting Using Bindings . It describes how most DataGrid formatting can be done with binding, including TextDecorations.
I have a WPF ComboBox that I bind to a list of custom objects, each of which contains an IsSelected property. This property is bound to its corresponding ComboBoxItem's IsSelected property. When initially creating the list, I set a particular object's IsSelected property to true, with the expectation that this item will be selected when the ComboBox is initially shown. However, the ComboBox always shows the first item in the list as selected, regardless of which object in its bound list has its IsSelected property set to true.
I know that the binding is working properly, because when I click the ComboBox and it expands to show all the available options, the ComboBox updates to show the correct selected item.
Here's the XAML the defines my ComboBox. It's in a DataTemplate, and is dynamically added/removed from the page, if that is at all relevant:
<ComboBox ItemsSource="{Binding DataContext.YearList, RelativeSource={RelativeSource AncestorType=Window}}" IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Object}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Try removing IsSynchronizedWithCurrentItem="True". That setting is telling the control to use the CurrentItem on the bound collection's generated ICollectionView, which defaults to the first item in the list. If you want to see what its doing you can play with this view in code by using CollectionViewSource.GetDefaultView(YearList) and then looking at or changing the current item.
Try binding to SelectedItem property, ComboBox will not select first item by default but your Source of data binding:
<ComboBox ItemsSource="{Binding YearList, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding <YourDefinedSelectedItemProperty>, RelativeSource={RelativeSource AncestorType=Window}}">
Hope this helps.
I would like to avoid having to build a menu manually in XAML or code, by binding to a list of ICommand-derived objects. However, I'm experiencing a bit of a problem where the resulting menu has two levels of menu-items (i.e. each MenuItem is contained in a MenuItem):
My guess is that this is happening because WPF is automatically generating a MenuItem for my binding, but the "viewer" I'm using actually already is a MenuItem (it's derived from MenuItem):
<ContextMenu
x:Name="selectionContextMenu"
ItemsSource="{Binding Source={x:Static OrangeNote:Note.MultiCommands}}"
ItemContainerStyleSelector="{StaticResource separatorStyleSelector}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<Viewers:NoteCommandMenuItemViewer
CommandParameter="{Binding Source={x:Static OrangeNote:App.Screen}, Path=SelectedNotes}" />
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
(The ItemContainerStyleSelector is from http://bea.stollnitz.com/blog/?p=23, which allows me to have Separator elements inside my bound source.)
So, the menu is bound to a collection of ICommands, and each item's CommandParameter is set to the same global target (which happens to be a collection, but that's not important).
My question is, is there any way I can bind this such that WPF doesn't automatically wrap each item in a MenuItem?
Unfortunately, the best way I've found to work around this issue is to use a style for the MenuItems, rather than an ItemTemplate. Then each property in the style can be bound to properties on your object. Something like this, for example:
<Style x:Key="SelectionContextMenuStyle" TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Text}" />
<Setter Property="Command" Value="{Binding Path=Command}" />
<Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
</Style>
It really seems like an ItemTemplate should work, and it would be the better way to go, but this is the only way I've found that actually works properly.
I would be inclined to subclass ContextMenu and override GetContainerForItemOverride:
public class ContextMenuWithNoteCommands : ContextMenu
{
protected virtual DependencyObject GetContainerForItemOverride()
{
return new NoteCommandMenuItemViewer();
}
}
Then set the CommandParameter binding in the NoteCommandMenuItemViewer style, or in ContextMenu.ItemContainerStyle, whichever is more appropriate.
This presumes you can't simply use ItemContainerStyle on a regular MenuItem to get the effect you want:
<ContextMenu ...>
<ContextMenu.ItemContainerStyle>
<Style>
...
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>