I am having problem that is probably related the collectionview. The problem seem to be that each group will keep track of its own selector, which will prevent the program from firing a new SelectionChanged event when I select a previously selected item in a previous group.
Example:
Group 1:
row1
row2(selected)
Group 2:
row1 (selected)
If I then first open group 1 and click on row 2, then swap to Group 2 and select the first row. If I then again go back to group 1 and clicks row 2 there won't be a trigger event and the selecteditem will stay the same from group 2, until I click row 1.
Which is a problem because there can be cases where there are only 1 row in a group.
My XAML looks like this (removed unrelated stuff).
<DataGrid
Margin="0,10,0,0"
SelectionMode="Single"
IsReadOnly="True"
Name="InvoiceGrid"
SelectedItem="{Binding SelectedDiscrepancy, UpdateSourceTrigger=PropertyChanged}"
RowDetailsVisibilityMode="{Binding RowDetailsVisible}"
ItemsSource="{Binding InvoiceDiscrepancies}">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" BorderBrush="#FF002255" >
<Expander.Header>
<StackPanel Orientation="Horizontal">
<Textblocks for design here.../>
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<Columns here/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<style for animating the expanding/>
</DataGrid.RowStyle>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Grid>
<ListBox Name="listBox3"
SelectionMode="Single"
HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource invoiceItemTemplate}"
ItemsSource="{Binding Invoices, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=DataContext.SelectedInvoice, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
</ListBox>
</Grid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
So, anyone know what might be the solution? Is it possible to have one selector between the groups, or should I somehow clear the selector when I select a new row in the parent datagrid?
EDIT:
Github repo with an example of the issue
https://github.com/Snuffsis/GroupingSelect
If you have no needs to keep all the selected listbox items you can just clear the selection.
As an option you can raise PropertyChanged with the null value to clear the current selection.
public Invoice SelectedInvoice
{
get => _selectedInvoice;
set
{
if (_selectedInvoice != value)
{
if (_selectedInvoice != null) // <- add the 'if' block
{
_selectedInvoice = null;
RaisePropertyChanged(() => SelectedInvoice);
}
_selectedInvoice = value;
RaisePropertyChanged(() => SelectedInvoice);
}
}
}
Related
I have a datagrid in my WPF application.
This datagrid is bound to an ObservableCollection which is called OperationClasses as defined below.
The type OperationClass consists of 3 properties which are Name, Start and End respectively.
I would like to know how to add OperationClass as a datagrid row to the DataGridList by clicking into the last row with the text "new Operation Class“ as shown in the image as an attached file.
DataGrid picture with example values:
After the end user has entered a name and a start and end value then the text "new Operation Class" will be displayed in the next line, which is the new last line.
How can I compose the static text "new Operation Class" with the databound objects listed in DataGrid?
public ObservableCollection<OperationClass> OperationClasses
{
get
{
return _operationClasses;
}
set
{
SetProperty(ref _operationClasses, value);
}
}
I tried to find a way to use CompositeCollection so as to compose databound objects(OperationClass) and the static text "New Operation Class". But i couldnt succeed in for the current stage..
First off all, I would like to thank you for valuable post.
But I have some issues regarding add new item operation. I defined the issues by sharing the corresponding images as shown below.
After user has entered a name and a start and end value then the text "new Operation Class“ should be displayed in the next line, which is the new last line. In other words, the properties which are Name,Start and End are compulsory columns to be filled for the placeholder to be displayed in the next line.
In addition to this, even though I can select the new Item place holder row as a selected row, I cant do it for data bound records. I will be very delighted if you can have the opportunity to inform me.
Before adding new Item
After adding new item
Selected New Item Row
Selected Data Bound Item Row
To make it easier, I am sharing the xaml code as shown below.
<UserControl.Resources>
<ResourceDictionary>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding DataContext.SelectedTreeItem.Name, Mode=OneWay}"></Setter>
</Style>
<Style TargetType="DataGridCell">
<Setter Property="BorderBrush" Value="Gainsboro"></Setter>
<Setter Property="BorderThickness" Value="0"></Setter>
</Style>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="{StaticResource ContentBackgroundColorBrush}"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="White"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="DarkBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<AdornerDecorator>
<Grid>
<DataGrid x:Name="OperationClassDataGrid"
ItemsSource="{Binding PlantMachine.OperationClasses, UpdateSourceTrigger=PropertyChanged}"
AlternationCount="2"
CanUserAddRows="True"
CanUserDeleteRows="True"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
AutoGenerateColumns="False"
IsReadOnly="False">
<DataGrid.Columns>
<DataGridTextColumn Visibility="Hidden" Header="{DynamicResource Plant.OperationClass.List.Header.Key}" Binding="{Binding OperationKey}" Width="Auto" SortDirection="Ascending"/>
<DataGridTemplateColumn Header="{DynamicResource Plant.OperationClass.List.Header.Name}">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type model:OperationClass}">
<TextBlock Text="{Binding OperationName}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<!-- Instead of defining the CellTemplate explicitly,
we assign the CellTemplateSelector instead -->
<DataGridTemplateColumn.CellTemplateSelector>
<local:OperationClassTemplateSelector>
<local:OperationClassTemplateSelector.DefaultTemplate>
<DataTemplate DataType="{x:Type model:OperationClass}">
<TextBox Text="{Binding OperationName}" />
</DataTemplate>
</local:OperationClassTemplateSelector.DefaultTemplate>
<local:OperationClassTemplateSelector.PlaceholderTemplate>
<DataTemplate>
<TextBlock Text="new operation class..." />
</DataTemplate>
</local:OperationClassTemplateSelector.PlaceholderTemplate>
</local:OperationClassTemplateSelector>
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
<DataGridTextColumn Header="{DynamicResource Plant.OperationClass.List.Header.Start}" Binding="{Binding RangeStartValue}" Width="Auto" SortDirection="Ascending"/>
<DataGridTextColumn Header="{DynamicResource Plant.OperationClass.List.Header.End}" Binding="{Binding RangeEndValue}" Width="Auto" SortDirection="Ascending"/>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="/Resources/Images/Delete.png" Width="125" Height="30" HorizontalAlignment="Right">
<Image.InputBindings>
<MouseBinding Gesture="LeftClick" CommandParameter="{Binding Path=Key}" Command="{Binding DataContext.RemoveOperationClassCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</Image.InputBindings>
</Image>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</AdornerDecorator>
</UserControl>
You need to provide a DataTemplateSelector to apply a dedicated DataTemplate to the placeholder column.
First of all, the placeholder item (or placeholder table row) must be enabled by setting DataGrid.CanUserAddRows to true. The value is true by default so just make sure you don't explicitly set it to false.
The following example assumes that you want the placeholder text to fill the first column. However, you can follow the below pattern to add a placeholder text for any column.
Following your code, the example assumes that the data model used to populate the DataGrid is the OperationClass with two fictional properties to make two columns:
OperationClass.cs
class OperationClass : INotifyPropertyChanegd
{
public string TextDataFirstColumn { get; set; }
public string TextDataSecondColumn { get; set; }
}
First, define the DataTemplateSelector:
OperationClassTemplateSelector.cs
class OperationClassTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate PlaceholderTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
=> item switch
{
OperationClass _ => this.DefaultTemplate,
var dataItem when dataItem is not null => this.PlaceholderTemplate,
_ => base.SelectTemplate(item, container),
};
}
Second step is to explicitly define column templates.
It is important to define the column that should contain the placeholder text as DataGridTemplateColumn. This is required in order to allow the assignment of a DataTemplateSelector implementation.
The first column of the placeholder row will display "Add new item..." as placeholder text:
<DataGrid ItemsSource="{Binding OperationClassItems}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="1st Column">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="{x:Type local:OperationClass}">
<TextBox Text="{Binding TextDataFirstColumn}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<!-- Instead of defining the CellTemplate explicitly,
we assign the CellTemplateSelector instead -->
<DataGridTemplateColumn.CellTemplateSelector>
<local:OperationClassTemplateSelector>
<local:OperationClassTemplateSelector.DefaultTemplate>
<DataTemplate DataType="{x:Type local:OperationClass}">
<TextBlock Text="{Binding TextDataFirstColumn}" />
</DataTemplate>
</local:OperationClassTemplateSelector.DefaultTemplate>
<local:OperationClassTemplateSelector.PlaceholderTemplate>
<DataTemplate>
<TextBlock Text="Add new item..." />
</DataTemplate>
</local:OperationClassTemplateSelector.PlaceholderTemplate>
</local:OperationClassTemplateSelector>
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
<DataGridTextColumn Header="2nd Column"
Binding="{Binding TextDataSecondColumn}" />
</DataGrid.Columns>
</DataGrid>
I am trying to display my EF query result (which has a group by clause) and display itinto a DataGrid. I am trying to copy the example here
http://wpftutorial.net/DataGrid.html
So here is my XAML code
<DataGrid ItemsSource="{Binding QueryResult}">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ItemCount}"/>
<TextBlock Text="Items"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
Here is my ViewModel
internal void GenerateReport()
{
Data = Project.GetReport(); **// this is List<IGrouping<string, MyType>>**
//QueryResult is a ListCollectionView property
QueryResult= new ListCollectionView(Data);
QueryResult.GroupDescriptions.Add(new PropertyGroupDescription("train"));
}
This is what my result of query looks like
I do get the key correctly but if you look at my code, I have a collection against the key item. THat is not displaying here. How do I get that to display against each train?
EDIT
I think I have more details on this. Here is how my QueryResult collection looks like
Basically my QueryResult has 10 items where each item has a Name, Items and ItemsCounts.
Each Items itself has 1 item inside which has a key and Group. This group basically contains the items that I want to display in my DataGrid as grouped against parent 10 records that are appearing.
I try to group some data in wpf datagrid .. it works fine, except the HEADER OF TEMPLATE DOESN'T DISPLAY any thing. It should display teacher name by TEACHER property. I use SQtOLinq as the underlayer data source.
what did I miss in my code?
XAML code:
<DataGrid AutoGenerateColumns="False" Height="311" Style="{StaticResource DashboardGridStyle}" HorizontalAlignment="Left" Name="TeacherDetailsDG" VerticalAlignment="Top" Width="322" ItemsSource="{Binding}" Margin="178,0,0,0" SelectionChanged="TeacherDetailsDG_SelectionChanged" RowBackground="#FF00E700" SelectionUnit="FullRow" BorderBrush="#FF00E400" AlternatingRowBackground="#FFC4B5B5">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel >
<TextBlock Text="{Binding Path=Teacher,Mode=TwoWay}" Foreground="Blue"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander x:Name="exp">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Teacher,Mode=TwoWay}" />
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter Visibility="{Binding ElementName=exp, Path=IsExpanded}" />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=TeacherID}" Visibility="Hidden">
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=Teacher}"/>
<!--Grades column-->
<DataGridComboBoxColumn x:Name="GradesCombo" ItemsSource="{Binding}" DisplayMemberPath="Grade" SelectedValuePath="ID" SelectedValueBinding="{Binding Path=GradeID}"></DataGridComboBoxColumn>
<!--Subjects column-->
<DataGridComboBoxColumn x:Name="SubjectsCombo" ItemsSource="{Binding}" DisplayMemberPath="Subject" SelectedValuePath="ID" SelectedValueBinding="{Binding Path=SubjectID}"></DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Datagrid Binding code:
public MainWindow()
{
InitializeComponent();
GradesCombo.ItemsSource = SchoolDC.GradesTables;
SubjectsCombo.ItemsSource = SchoolDC.SubjectsTables;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView((SchoolDC.TeachersDetailsSP().ToList<object>()));
view.GroupDescriptions.Add(new PropertyGroupDescription("Teacher"));
TeacherDetailsDG.ItemsSource = view;
}
catch (Exception)
{
throw;
}
}
This is the result:
GroupItem.Name gets the value of the property you grouped by .
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" Foreground="Blue"/>
</DataTemplate>
If you are using ReSharper, (and you most definitely are, aren't you?) the problem with the accepted answer is that the reference to Name will be flagged with "Cannot resolve property 'Name' in data context of type 'Xyz'" where Xyz is the type of the enclosing data context, most probably the ViewModel of your screen.
(Or, your enclosing data context might also have a property called 'Name', in which case you will be oblivious to the discrepancy.)
To remedy this problem, change <DataTemplate> to <DataTemplate DataType="GroupItem"> to let WPF (and ReSharper) know what is the actual type of the data context that the DataTemplate is going to be applied to.
Then, the Name property will not be flagged anymore, and as a matter of fact, if you choose "Go to definition" on the property you will be taken to the Name property of UIElement, from which GroupItem inherits it. (Assuming that you are, of course, using ReSharper, instead of just plain woefully inadequate Visual Studio, right?)
I have this xaml. And I need to uncheck all other checkboxes where one is checked. I other words to allow to check only one. I add TreeViewItems on a runtime.
<TreeView Name="treeView_max" >
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="chk" Margin="2" Tag="{Binding}" Checked="checkBox_Checked">
</CheckBox>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>
Adding TreeViewItems at runtime:
foreach (Genesyslab.Desktop.Modules.Core.Model.BusinessAttributes.IDispositionCodeValue item in listOfDispositionCodeValueItemsControl.Items)
{
TreeViewItem newChild2 = new TreeViewItem();
newChild2.Header = item.DisplayName.Remove(0,item.DisplayName.IndexOf("-") + 1);
treeView_max.Items.Add(newChild2);..........`
and
private void checkBox_Checked(object sender, RoutedEventArgs e)
{
try
{
//uncheck all checkboxes except selected one
}
catch (Exception es)
{
MessageBox.Show(es.ToString());
}
}
You can use RadioButton controls that belong to the same group instead, which will get you the behavior of only one option being able to be selected at a time.
Then edit the control template to display CheckBox controls in place of those RadioButton's, and bind the IsChecked property of each CheckBox to its parent RadioButton. Now when you "check" a CheckBox, all other CheckBox controls will become unchecked.
<TreeView Name="treeView_max" >
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<RadioButton Name="chk" Margin="2" Tag="{Binding}" GroupName="SomeGroup">
<RadioButton.Template>
<ControlTemplate>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=RadioButton}}" />
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>
Be careful about where you use this. Users are used to seeing RadioButton's when they're only able to select one option, and CheckBox's where they can select multiple options.
I have currently populated a datagrid with certain amount of rows and grouped the rows based on department.
So ,all I can see in my output is a "Department-based Grouped Datagrid".
Is it possible to toggle between grouping and non grouping of datagrid rows ?
For Example : If a user doesnt wants to see records based on groups, he'll click on radiobutton and datagrid will populate rows without grouping and vice-versa.
Thanks in advance.
Here is sample code inside DataGrid.GroupStyle :
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander Margin="15 0 0 0" IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="white" Text="{Binding Path=emp}" FontWeight="Bold" Margin="5 0 0 0"/>
<TextBlock Foreground="white" Text="{Binding Path=empCount}" Margin="10 0 3 0"/>
<TextBlock Foreground="white" Text="emps"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
Sorry if this is old, but I might as well answer.
The edit you need to do are not in your GroupStyle but in the code of your ViewModel. You probably bind your listview ItemSource to an ObservableCollection<>. Internally A view of your collection is made, and this collectionView is the one that is actually bound to your ListView. You can explicitly obtain that view in this way:
public ObservableCollection<T> HeldCollection{ get; set; }
#region properties for the view
CollectionView _HeldView;
public CollectionView HeldView
{
get
{
if (_HeldView == null)
{
_HeldView = (CollectionView)CollectionViewSource.GetDefaultView(HeldCollection);
//_HeldView.GroupDescriptions.Add(GroupDescription);
}
return _HeldView;
}
}
You can then edit the GroupDescription since it is a property of the CollectionView, for example in response to a button toggle.