WPF + MVVM: Override autocomplete in DataGridComboBoxColumn - c#

I've been searching for quite a while, but cannot figure out how to override the autocomplete functionality in a DataGridComboBoxColumn.
What I want to do is what is explained here, except for a combobox: Searching for items in a list box
That is: I want to be able to enter any string and then apply a filter to the ComboBox items in the DataGridComboBoxColumn to show only those items that match this as a substring.
I'm new to WPF and have been searching online for a while. I've found things like EventSetters and CommandBehaviorCollection.Behaviors, but I can't get a clear picture of the possibilities (and impossibilities).
I've got:
<DataGrid ... >
...
<DataGrid.Columns>
...
<MyCustomDataGridComboBoxColumn Header="My Header" MinWidth="200" >
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding DataContext.MyData, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<Setter Property="SelectedItem" Value="{Binding DataItem, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True}" />
<Setter Property="DisplayMemberPath" Value="HardwareId" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding DataContext.MyFilteredData, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<Setter Property="SelectedItem" Value="{Binding DataItem, UpdateSourceTrigger=LostFocus}" />
<Setter Property="DisplayMemberPath" Value="HardwareId" />
<Setter Property="IsEditable" Value="True"/>
<Setter Property="Text" Value="{Binding DataContext.MyNewDataItem, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</MyCustomDataGridComboBoxColumn>
...
</DataGrid.Columns>
</DataGrid>
Ideally, I'd like to create a new class that inherits from DataGridComboBoxColumn and supply it with some custom logic, such as supplying an anonymous function in its constructor so that the autocomplete behavior can be overridden in different ways in the future.
Is this even possible, or am I going about this entirely the wrong way?

I'm not saying your approach is wrong, however, I would approach it differently. For me it seems easier to use a DataGridTemplateColumn and supply a ComboBox that has the functionality you speak of.
<DataGridTemplateColumn Header="ColumnName" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<YourCustomComboBox/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Edit:
A while ago I needed a ComboBox with the same functionality. I ended up combining a TextBox with a Popup control because it gave me much more control over it.
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" x:Name="editBox"/>
<Popup x:Name="textboxPopup" Width="{Binding ElementName=editBox, Path=ActualWidth, Mode=OneWay}"
PlacementTarget="{Binding ElementName=editBox}"
StaysOpen="False"
IsOpen="{Binding Path=IsOpen, Mode=OneWay}">
<Grid>
<DockPanel MaxHeight="500">
<ListView SelectionMode="Single"
ItemsSource="{Binding Path=Suggestions}"
Name="popupList">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"Color="LightBlue"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="LightBlue"/>
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="LightBlue"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</DockPanel>
</Grid>
In the codebehind I subscribed to the TextChanged event and a few other events that came in handy. I can't share all my code because it is production code. However there are a few other people on the internet with similar implementations: using a ComboBox, using a textbox and of course the link you posted in your question. There is more than enough out there.
And about using your custom control as a TargetType... I don't see anything wrong with that, I do it all the time.
The error with the CellTemplate shouldn't occur. Are you using it correctly? See this link for an example.

Related

Binding to DataGridComboBox

I know this has been asked a few times, but I cannot get the binding to work on my DataGridComboBox, it never displays at all. Can someone show me the error of my ways?
c#
IList<ServiceCodes> servicecodes = App.GetInfo.GetServiceCodes();
newinvoice.INVItemsDataGrid.DataContext = servicecodes;
newinvoice.ShowDialog();
XAML
<DataGrid x:Name="INVItemsDataGrid" DataContext="{Binding}">
<DataGrid.Columns>
<DataGridComboBoxColumn x:Name="INVSCDropDown" DisplayMemberPath="CodeName" SelectedValuePath="CodeName" SelectedValueBinding="{Binding CodeName}" />
</DataGrid.Columns>
</DataGrid>
Thanks for your help as always.
The first thing you need to do is to set the ItemsSource property of the DataGrid to an IEnumerable.
Once you have done this, you could bind the ComboBox to another or the same IEnumerable like this:
<DataGrid x:Name="INVItemsDataGrid" ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridComboBoxColumn x:Name="INVSCDropDown" DisplayMemberPath="CodeName" SelectedValuePath="CodeName" SelectedValueBinding="{Binding CodeName}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=., RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=., RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
...although it doesn't make much sense to bind the ComboBox and the DataGrid to the same source collection. But you should at least get the idea.

DataTrigger with Property = ItemControl.ItemTemplate

I Am having some trouble getting this binding to work. I have several objects within SettingCollection that all have an enum property. I want to generate a control based on what the value of this is. But when I check this value with a data trigger it does not work.
Can anyone provide some insight into how i can accomplish this?
<Window.Resources>
<DataTemplate x:Key="CheckboxNode">
<CheckBox IsChecked="{Binding Status}" Margin="0,5,0,0">
<ContentPresenter Content="{Binding DisplayName}"/>
</CheckBox>
</DataTemplate>
<DataTemplate x:Key="TextboxNode">
<TextBox Text="Badgers"></TextBox>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding SettingCollection}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="checkbox">
<Setter Property="ItemsControl.ItemTemplate" Value="{StaticResource CheckboxNode}" />
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="textbox">
<Setter Property="ItemsControl.ItemTemplate" Value="{StaticResource TextboxNode}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>
If Type property is in SettingNode class, and SettingCollection is a collection of SettingNode objects, then your binding in the Datatriggers is incorrect. DataTriggers will look for Type property in ItemsControl DataContext (class with SettingCollection). Try to use DataTemplateSelector https://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector(v=vs.110).aspx

ListBox Loading Animation

I have a list box that should be filed when user click on Button,
sometimes the data loaded is quickly, and sometimes it take some time... Is there a simple way to load some animation like a clock, or something which can give the user indication that the process is running?
I use mvvm with button commands
<ListBox Width="30"
Visibility="{Binding IsDataLoaded,
Converter= {StaticResource BooleanToVisibilityConverter}}"
ItemsSource="{Binding Collection}"
FontSize ="15"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="DarkGray" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<Button Content="Go" Command="{Binding GoCommand}" IsEnabled="{Binding IsGoEnabled}" IsDefault="True" Width="60"
/>
Sounds like you're looking for a BusyIndicator like in the Extended Toolkit.

DataGridTemplateColumn.HeaderTemplate issue

Please help on this issue and I'm not sure how to handle this.
I've combox box and datagrid. When ever I've selected combox value datagrid should be loaded with new data and that works perfectly. But I've DataGridTemplateColumn.HeaderTemplate with checkbox when I checked all the column with checkbox is also checked and also worked with unchecked as well. Both are fine.
Now my issue is when i selected combobox, datagrid --> headertemplate checkbox should be unchecked. IS there any event I can fire for this? My code below.
Combox is outside the datagrid.
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="checkadded" Margin="6,0" IsChecked="{Binding IsSelected, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Name="ChkAllAdd" IsChecked="False" Width="50" Loaded="chkallLoaded" Checked="ChkAll_Checked" Unchecked="ChkAll_Unchecked" IsThreeState="False" Padding="4,3,4,3" HorizontalContentAlignment="Left" HorizontalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Foreground" Value="#686868"/>
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style TargetType="{x:Type DataGridCell}" >
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="white"/>
<Setter Property="Background" Value="#93A8A9"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
Create a ControlTemplate for Content Control and place your DataGrid and ComboBox Xaml in that. Now you can create an EventTrigger for Combobox and there you can set the CheckedBox Checked using element name in binding. This can be helpfull.

ComboBox template problems in WPF

I have a listview with a DataTemplate that has a ComboBox. I want the ComboBox to look flat like a label until the user actually wants to change the value. I had the example below working before, but I changed things around a bit, and now it doesn't work anymore and I'm not sure why.
The IsMouseOver property does not seem to be working correctly, as it only gets set when the mouse is right at the border of the control.
What can I do to make this work correctly?
Here is a snippet:
<CollectionViewSource x:Key="AccountCategories" />
<ControlTemplate x:Key="FlatCombo" TargetType="{x:Type ComboBox}">
<ContentControl
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
Margin="4,3,3,3"
/>
</ControlTemplate>
<Style TargetType="{x:Type ComboBox}" x:Key="DropDown">
<Setter Property="OverridesDefaultStyle" Value="False" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False" />
<Condition Property="IsFocused" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="Template" Value="{StaticResource FlatCombo}" />
</MultiTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="Category">
<ComboBox IsSynchronizedWithCurrentItem="False" Style="{StaticResource DropDown}"
ItemsSource="{Binding Source={StaticResource db}, Path=Categories}" DisplayMemberPath="Name" SelectedValuePath="Id" SelectedValue="{Binding Path=Category}" />
</DataTemplate>
</Window.Resources>
<Grid>
<ListView Margin="0,110,0,0" Name="lstCategories" ItemsSource="{Binding Source={StaticResource AccountCategories}}" Grid.RowSpan="2">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Category" Width="100" CellTemplate="{StaticResource Category}" />
<GridViewColumn DisplayMemberBinding="{Binding Path=Balance}" Header="Balance" Width="100" />
</GridView>
</ListView.View>
</ListView>
I took the code you provided, supplied some data for the collections, and it worked just like you wanted it to. I would suggest using Snoop to look to see if there are any other elements consuming the events you expect the ListView to handle.
Usually when your are having issues with Mouse events firing correctly it's due to the background missing. If the element you expect to receive events has a null reference for a background, the control will not receive the events; only the control underneath of it will. Try setting the background of your ContentControl to "Transparent". That should fix your problem.

Categories