Update SortMemberPath on property change - c#

I have a property, SortingName, used as the SortMemberPath for one of my columns in my DataGrid. However, I would like this property to get updated every time the header is clicked (i.e. call its setter again).
Here is the property:
[XmlIgnore]
public virtual string SortingName {
get { return m_sortingName; }
set
{
if (m_sortingName == null)
m_sortingName = value;
m_sortingName = m_sortingName.StartsWith("_") ? value : "_" + value;
}
}
And here is the XAML for the DataGrid it's used in (the first column is the :
<DataGrid.Columns>
<DataGridTemplateColumn Header="Templates"
Width="200" SortMemberPath='SortingName'>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type request:ModelDocument}">
<TextBlock TextAlignment="Left">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding PrettyName}"/>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...remaining columns
I've tried to use a Binding for the SortMemberPath, and assigning an UpdateSourceTrigger=PropertyChanged, but it did not seem to do anything. I had also attempted this method, but had issues with the how my ItemsSource is defined. So I would prefer to update the property on click instead.

To solve this I simply created a function in my custom class that extends off DataGrid, and added it to its Sorting event handler.
public DataGridEx()
{
Sorting += UpdateSorting;
}
The UpdateSorting function appends an ASCII character at the beginning of the string depending on what the order I'd like the item to stay in. In this example I want folder type items to stay at the top, so when the list is ascending I add a low alphabetical character (e.g. !), and vice versa for the descending list. This way the folders are always at the top.
This isn't the best solution in my opinion (and honestly not very related to the question itself) but it works for now. If anyone has a better answer please feel free to post!

Related

Multiple DataGrid columns with the same custom template/style but different binding/context

I would like to create a custom DataGrid column (or a DataGridCell style), ideally in XAML, and use it multiple times without any code duplication. It should contain multiple different controls, each accessing a different property of the data object.
There seem to be two ways of doing this:
DataGridTemplateColumn with a custom CellTemplate (see an example here)
custom DataGridCell style (something like this or this)
Both of these solutions suffer from the same issue. When specifying the bindings for the internal controls, they always seem to be related to the whole DataGrid row rather than the cell.
DataGridTemplateColumn does not have a Binding attribute, so its CellTemplate is bound to the whole row, making it impossible to reuse the same template for multiple columns, each accessing a different property of the row object.
Similarly, I can access the content of DataGridTextColumn in a style that overrides the Template via {TemplateBinding Content} (see here), but I cannot find a way to access the content's properties, so I assume that is not possible.
Here is a dummy example of such DataGrid to illustrate my situation:
public class Name
{
public string First { get; set; }
public string Last { get; set; }
public override string ToString() => $"{First} {Last}";
}
public class Team
{
public Name First { get; set; }
public Name Second { get; set; }
public Name Third { get; set; }
}
public ObservableCollection<Team> DataList { get; } = new ObservableCollection<Team>();
<DataGrid ItemsSource="{Binding DataList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First" Binding="{Binding First}"/>
<DataGridTextColumn Header="Second" Binding="{Binding Second}"/>
<DataGridTextColumn Header="Third" Binding="{Binding Third}"/>
</DataGrid.Columns>
</DataGrid>
Now, I would like to separate the First and Last properties of Name and render them using individual controls, both still inside of the same DataGrid cell. The motivation here could be to have First in black and Last in red, for example, or anything else that requires them to be in two separate controls.
Example of a DateTemplate (applied as CellTemplate on DataGridTemplateColumn):
<DataTemplate x:Key="NameTemplate">
<StackPanel>
<Label Content="{Binding First}"/>
<Label Content="{Binding Last}"/>
</StackPanel>
</DataTemplate>
Example of a DataGridCell style (applied as CellStyle on a DataGridTextColumn):
<Style x:Key="NameStyle" TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<DockPanel>
<ContentPresenter Content="{Binding First}"/>
<ContentPresenter Content="{Binding Last}"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Is there any way to make a code similar to either of the examples above work?
Of course, it is possible to copy-paste the same exact template/style for each column, each accessing the relevant row-level property of Name, but this is an awful solution that would be a nightmare to maintain and it just does not scale.
I have seen a handful of similar questions, but not a single one of them had a XAML solution that was not extremely complex. This just seems like something that should be pretty easy to do, but I am unable to find a straightforward solution anywhere. The most similar question I found was probably this, but it was never really answered in a way that would help my case.
Two possible solutions were to either build and apply the XAML code (template/style) at runtime or to add an attached property to the column, which would allow me to pass the properties separately. Both of those seem like terrible hacks to me and I would much prefer avoiding them. Another solution is to create a new control (as suggested here) derived from DataGridBoundColumn (such as this), but I had no luck with that either and it a fairly complicated solution compared to defining a DataTemplate.
PS: While finishing up the question and looking for the links to other questions and answers, I have stumbled upon the DataGridBoundTemplateColumn implementation, which pretty much works, but I would still prefer a more XAML-native solution using just a template/style rather than creating a new control that still requires the template on top of itself.

Disable selection of specific item in DatagridComboBox

Background:
I have a Datagrid with some Measurements and this Measurements we can Approve and Block.
Now we have for this a new Type, like "Cancelled". But this Type is only needed by Server and for displaying it to Customer.
But the Customer should not be able to select this "Cancelled" but the other 2 Types he should have to select.
The List get all different elements from Database (3 entries).
Firstly i tried to remove the Cancelled Item from the ApprovementCollection, but then it displayed a empty field instead of "Cancelled".
Question:
Is it possible, to disable only one of this 3 Items in the Bounded List of the Itemsource Property from the DataGridComboBoxColumn?
Disabled or Not Displayed in the Selection Menu is that what i have to do.
(Only "Freigabe" and "GESPERRT")
View:
<DataGridComboBoxColumn ClipboardContentBinding="{x:Null}"
DisplayMemberPath="ApprovementText"
Header="{x:Static trans:Translations.ClearenceHeader}"
ItemsSource="{Binding Source={StaticResource PossibleComponentMeasurementApprovements}}"
SelectedItemBinding="{Binding Approvement,
UpdateSourceTrigger=PropertyChanged}" />
Viewmodel:
private IEnumerable<ComponentMeasurementApprovement> possibleComponentMeasurementApprovements;
public IEnumerable<ComponentMeasurementApprovement> PossibleComponentMeasurementApprovements {
get { return possibleComponentMeasurementApprovements; }
set {
possibleComponentMeasurementApprovements = value;
OnPropertyChanged();
}
}
Thanks for your Help
This is possible writing a DataGridTemplateColumn for your cell instead of using the DataGridComboBoxColumn. Just add a property Enabled to your ComponentMeasurementApprovement class. This property indicates if the user is allowed to select the approvement.
Now create a new DataGridTemplateColumn containing a ComboBox as template. It is possible to bind IsEnabled of every ComboBox item to a proeprty by styling them via ItemContainerStyle.
Here is the code:
<DataGridTemplateColumn Header="CustomCell">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={x:Static local:ViewModel.PossibleComponentMeasurementApprovements}}"
DisplayMemberPath="ApprovementText"
SelectedItem="{Binding Approvement}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding Enabled}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is the result:
Since the second item is disabled, it's not possible to select it but an already selected item keeps selected.

How to properly bind a ListView so that it's items can receive a background value?

I'm currently working on a small Video Poker app, partially because it's fun & because I needed a project for school.
Unless I missed something, my logic should be done, but now, as I'm turning my attention to the UI aspect of the app, I've hit a snag.
What I need/want my ListView to do is this:
Not interact with mouseovers/mouse clicks performed by the user
Be filled through code, the items need to be replaced each time the bet changes
When the user has a winning hand, highlight it in the ListView by changing the BackGround property.
So far I've had "some" success, meaning I've been able to have it work so that I can select an item through mouse-clicks, after which the color changed, however I couldn't get it to work by changing the SelectedItem to the winning hand.
I've tried just about anything I can think of & I've hit a dead end.
I'm fairly sure it's somehow achieved through Binding data, but all my efforts so far have failed. I still lack quite a bit of both knowledge & experience when it comes to Databinding XAML elements.
If anyone here could help me out with a neat/clean/efficient way to achieve this, it would be greatly appreciated.
XAML:
<ListView x:Name="lvTest" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="160" Margin="10,10,0,0">
<ListView.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<GridViewRowPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Width="105" DisplayMemberBinding="{Binding HandNaam}"/>
<GridViewColumn Width="45" DisplayMemberBinding="{Binding Betaling}"/>
</GridView>
</ListView.View>
</ListView>
C#:
List<Hand> hands = new List<Hand>();
//Bet has been changed, (re-)populate the ListView
void Betchanged()
{
hands.Clear();
lvTest.ItemsSource = null;
for (int i = 0; i <= 8; i++)
{
hands.Add(new Hand(Hand.hands[i], bet));
}
lvTest.ItemsSource = hands;
}
//Game ends
//After checking for a winning hand, highlight that hand in the ListView
private void EndGame()
{
//Do some stuff
//Check if there's a winning hand
int winnings;
Hand hand = null;
winnings = Poker.FindWin(splK, hands, out hand); //out hand is of type Hand
//If there's a winning hand, highlight it in the ListView
if (hand != null)
{
foreach (Hand h in lvTest.Items)
{
if (h.HandName == hand.HandName)
{
//This seems like the most logical (maybe not most efficient) way to find the correct item in the ListView
//Here's where the Background property for the ListViewItem should be changed
}
}
}
//Do some more stuff
}
PS: The current Template style in my XAML is what's overriding my ListViewItem's hover/click interactions.
Edit: Here's a screenshot with a possible scenario when the Background for a ListViewItem should be changed:
Your ItemsSource in order to notify the control via the Binding that it's contents have changed, needs to implement INotifyCollectionChanged. At the moment, the most common generic collection to implement this interface is ObservableCollection<T>.
hands should be an ObservableCollection<Hand> rather than a List<Hand>.
Edit based on comment:
WPF Binding works on notifications of changes. For properties, this means implementing INotifyPropertyChanged on classes that need to notify that their properties have changed. Your Hand class should implement INotifyPropertyChanged for when the specific value you want to change has changed.
Presentation logic should be handled by the View class (or code related to the View like a Converter) so you should bind to your Model property value and convert it to a Brush object using a Converter.
You could just expose a Brush on your Model object but that would be breaking MVVM pattern as a ViewModel is not supposed to have View-specific/presentation technology classes embedded in them in order to be presentation technology agnostic.
Second edit based on second comment:
Converters are code that convert values from one type to the other by implementing IValueConverter interface ( MSDN : IValueConverter documentation ). They need to be declared as resources in your XAML and referenced using StaticResource directives in the Binding.
An excellent tutorial on this (and other basic WPF subjects) can be found at WPFTutorial.com - Value Converters

Is it possible to exposed the DataGridComboBoxColumn immediately?

Is it possible to have a WPF Toolkit Data Grid's DataGridComboBoxColumn "exposed" when the Data Grid loads? By default you have to click in the cell to expose the combo box. I'd like the user to see that the combo box is available without having to click in the cell. I would prefer that the combo box be immediately available and the first click in the cell makes the combo box actually drop down. Currently you have to click the cell and then click the combo box drop down to expose the values.
V.S.
XAML:
<dg:DataGridComboBoxColumn x:Name="ctrlStatus" Header="Status" Width="Auto" SelectedValueBinding="{Binding Port}" SelectedValuePath="Status">
<dg:DataGridComboBoxColumn.CellStyle>
<Style TargetType="dg:DataGridCell">
<EventSetter Event="Selector.SelectionChanged" Handler="SelectionChanged"/>
</Style>
</dg:DataGridComboBoxColumn.CellStyle>
</dg:DataGridComboBoxColumn>
Code Behind:
List<string> _statusList;
public List<string> StatusList
{
get
{
return _statusList;
}
set
{
_statusList = value;
ctrlStatus.ItemsSource = _statusList;
}
}
Thanks, GAR8
Final SOLUTION:
XAML
<telerik:GridViewComboBoxColumn Header="Status">
<telerik:GridViewComboBoxColumn.CellTemplate>
<DataTemplate>
<telerik:RadComboBox ItemsSource="{Binding StatusList,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}}" SelectedValue="{Binding Port}" SelectedValuePath="Status" SelectionChanged="SelectionChanged"/>
</DataTemplate>
</telerik:GridViewComboBoxColumn.CellTemplate>
</telerik:GridViewComboBoxColumn>
Code Behind:
List<string> _statusList;
public List<string> StatusList
{
get { return _statusList; }
set { _statusList = value; }
}
You can use a DataGridTemplateColumn and place a ComboBox as the cell edit template without specifying a non-edit template. This will let the DataGrid use always the ComboBox.
Update
As requested in your comment, below an example. Please note that the example is not optimal and I would have choosen another design, but I have done it in a way so that it should integrate in your solution without bigger problems. I have not tested it. Make a comment if they are errors in.
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate>
<ComboBox x:Name="ctrlStatus"
SelectedValueBinding="{Binding Port}"
SelectedValuePath="Status">
SelectionChanged="SelectionChanged"
ItemsSource="{Binding StatusList,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"
/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
To use the above code, StatusList must implement change notification. If your DataGrid is not in aWindow but in another class such as in a UserControl, replace the type name in the relative source.
Try this
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource=”{Binding Path=YourSource...}”
Text=”{Binding Path=YourSource...}”/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate>
<ComboBox ItemsSource=”{Binding Path=YourSource...}”
Text=”{Binding Path=YourSource...}”/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
Basically you define the ComboBox in both cases which are CellTemplate and CellEditingTemplate.
See this post which I wrote some time ago, in that I wrote separate template for non editing( which you see initially) and editing (which you see when you click i.e combobox) state of cell. Now you can copy the code of editing in non-editing as I have done in XAML above and your problem will be solved
If you need to use this often, then a custom column can be defined:
public class DataGridCustomComboBoxColumn : DataGridComboBoxColumn
{
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
return base.GenerateEditingElement(cell, dataItem);
}
}
This can then be used in place of the normal DataGridComboBoxColumn.

Bug with DataBinding in WPF Host in Winforms?

I've spent far too much time with this and can't find the mistake. Maybe I'm missing something very obvious or I may have just found a bug in the WPF Element Host for Winforms.
I am binding a ListView to a ObeservableList that lives on my ProductListViewModel.
I'm trying to implement searching for the ListView with the general Idea to just change the ObservableList with a new list that is filtered.
Anyway, the ListView Binding code looks like this:
<ListView ItemsSource="{Binding Path=Products}" SelectedItem="{Binding Path=SelectedItem}" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the ViewModel code is as vanilla as it can get:
private ObservableCollection<ProductViewModel> products;
public ObservableCollection<ProductViewModel> Products
{
get { return products; }
private set
{
if (products != value)
{
products = value;
OnPropertyChanged("Products");
}
}
}
Now the problem here: Once I debug into my OnPropertyChanged method, I can see that there are no subscribers to the PropertyChanged event (it's null), so nothing happens on the UI..
I already tried Mode=TwoWay and other Binding modes, it seems I can't get the ListView to subscribe to the ItemsSource...
Can anyone help me with this? I'm just about to forget about the ElemenHost and just do it in Winforms
greetings Daniel
Is there any binding error in the output window?
By the way, you should consider getting the collection view wrapping your products, and then filtering the view, instead of replacing the whole collection.
The code would be something like:
var collectionView = CollectionViewSource.GetDefaultView(Products);
collectionView.Filter += item => ...;

Categories

Resources