In my WPF View, I have a DataGrid which Display Data from a Database.
One column is a combobox that is bound to a string in Database.
This is the list of available string :
public List<string> AvailableTypeImpression { get; } = new List<string>() {"S","D","T","X","M","p","P","R"}
All these string could be displayed in the datagrid, but if the user want to edit it, I can only set S, D, T, or X. The user is not allowed to set M, p, P or R.
So I would like to hide these four letter from the combobox available Items. But I don't really know how to do that in a simple way (I found some solution on Stack Overflow but it doesn't work in my case).
Here's the code of my datagrid :
<DataGrid Grid.Row="1" x:Name="LotsListDataGrid" IsReadOnly="False" SelectionMode="Single" SelectionUnit="FullRow" SelectedItem="{Binding SelectedLot}" ItemsSource="{Binding FilteredList}" VirtualizingPanel.IsVirtualizing="True" EnableRowVirtualization="True" HorizontalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridTextColumn Header="Lot" Width="200" Binding="{Binding Value.Lot.Intitule}" IsReadOnly="True"/>
<DataGridTemplateColumn Header=".i." HeaderStyle="{StaticResource CenteredColumnHeaderStyle}" Width="545" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Height="23" Style="{StaticResource AnfComboBoxStyle}" ItemsSource="{Binding DataContext.AvailableTypeImpression, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" SelectedItem="{Binding Value.Lot.TypeImpression, UpdateSourceTrigger=PropertyChanged}">
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
FilteredList.Value.Lot.TypeImpression is a string.
If I've well understood, all the possible string must be in the ItemSource otherwise they could'nt be displayed. But I need to find a way to prevent user to select some of them.
Thanks for your help.
I don't know of a way to make individual items non-selectable in a combo box.
But you could always show a combo box without your not-allowed letters (i.e. remove them from AvailableTypeImpression), and instead show just a simple TextBlock in your cell in case one of these letters is read from the DB.
Change your cell template to something like this:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Visbility="{Binding IsValueEditable}" Height="23" Style="{StaticResource AnfComboBoxStyle}" ItemsSource="{Binding DataContext.AvailableTypeImpression, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" SelectedItem="{Binding Value.Lot.TypeImpression, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Visbility="{Binding NotIsValueEditable}" Height="23" Text="{Binding Value.Lot.TypeImpression}" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Then implement IsValueEditable, NotIsValueEditable properties in your ViewModel such that only one of the controls is shown.
Alternatively, if you also want to be able to modify existing entries of M, p, P, R, you could use a DataGridTemplateColumn.CellEditingTemplate:
Make the CellEditingTemplate show the ComboBox (without M, p, P, R options) while the regular CellTemplate only contains a TextBlock.
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Height="23" Text="{Binding Value.Lot.TypeImpression}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox Height="23" Style="{StaticResource AnfComboBoxStyle}" ItemsSource="{Binding DataContext.AvailableTypeImpression, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" SelectedItem="{Binding Value.Lot.TypeImpression, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
The solution from #dheller was good but not working in my case because the user is not able to give to "Value.Lot.TypeImpression" a new Value M ,p, P or R, but he's able to edit "Value.Lot.TypeImpression" even if it equals to M ,p, P or R and set S, D, T, or X.
So I've found another way :
In my ViewModel I've defined a new class :
public class TypeImpressionAvailable
{
public string Name { get; set; }
public bool IsAvailable { get; set; }
}
And my list to give to my combobox as ItemSource :
public List<TypeImpressionAvailable> AvailableTypeImpression { get; } = new List<TypeImpressionAvailable>() {
new TypeImpressionAvailable(){ Name="N", IsAvailable=true },
new TypeImpressionAvailable(){ Name="S", IsAvailable=true },
new TypeImpressionAvailable(){ Name="D", IsAvailable=true },
new TypeImpressionAvailable(){ Name="T", IsAvailable=true },
new TypeImpressionAvailable(){ Name="X", IsAvailable=true },
new TypeImpressionAvailable(){ Name="M", IsAvailable=false },
new TypeImpressionAvailable(){ Name="p", IsAvailable=false },
new TypeImpressionAvailable(){ Name="P", IsAvailable=false },
new TypeImpressionAvailable(){ Name="R", IsAvailable=false }
};
And in my View :
<DataGridTemplateColumn Header=".i." HeaderStyle="{StaticResource CenteredColumnHeaderStyle}" Width="45" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Height="23" SelectedValuePath="Name" DisplayMemberPath="Name"
SelectedValue="{Binding Value.Lot.TypeImpression, UpdateSourceTrigger=PropertyChanged}"
>
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource AnfComboBoxStyle}">
<Setter Property="ItemsSource" Value="{Binding DataContext.AvailableTypeImpression, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsAvailable}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And it works fine :)
I'm trying to have a datatrigger fire when I update a property on my DataGrid for the SelectedItem. Can someone show me what I need to change to get it to work?
When a user selects an item in the grid, its Connected property gets set to true. My label in the DataGrid is supposed to change from red to green. It stays red.
I know the style works because if I start out with the property Connected set to true, they all come out green. It just does not seem to fire when the user selects an item in the DataGrid.
// Style set in Resources of my control
<Style x:Key="ConnectedStyle" TargetType="{x:Type Label}"
BasedOn="{StaticResource FontAwesome}">
<Setter Property="Foreground" Value="Red" /> // I added this last
minute thinking it might need to be initialized but still nothing.
<Style.Triggers>
<DataTrigger Binding="{Binding Connected}" Value="True">
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Connected}" Value="False">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataGrid Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding AvailablePorts}"
SelectedItem="{Binding SelectedPort}"
HorizontalAlignment="Stretch"
SelectionMode="Single"
Style="{StaticResource PlainGrid}"
AutoGenerateColumns="False" ColumnHeaderHeight="30">
<DataGrid.Columns>
<DataGridTemplateColumn Header=" Status" Width="55">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Style="{StaticResource ConnectedStyle}"
Content="" FontSize="12" Margin="5,0,0,0"
Padding="0" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header=" Port" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Port}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
View Model
public class EMSViewModel : ViewModelDetailBase<EMSViewModel, EMSModel>
{
public ObservableCollection<AvailablePortsModel> AvailablePorts
{
get
{
return this.availablePorts;
}
private set
{
this.availablePorts = value;
this.NotifyPropertyChanged(m => m.AvailablePorts);
}
}
public AvailablePortsModel SelectedPort
{
get
{
return this.selectedPort;
}
set
{
this.selectedPort = value;
this.NotifyPropertyChanged(m => m.SelectedPort);
this.ConnectToPort();
}
}
private void ConnectToPort()
{
// if the code (removed) evaluates to true, set the connected property to true.
this.SelectedPort.Connected = true;
this.NotifyPropertyChanged(m => m.AvailablePorts);
}
}
Model
namespace FastV.Models
{
using SimpleMvvmToolkit;
public class AvailablePortsModel : ModelBase<AvailablePortsModel>
{
private bool connected;
private string port;
public bool Connected
{
get
{
return this.connected;
}
set
{
this.connected = value;
this.NotifyPropertyChanged(m => m.Connected);
}
}
public string Port
{
get
{
return this.port;
}
set
{
this.port = value;
this.NotifyPropertyChanged(m => m.Port);
}
}
}
}
My object has property that stores more strings separated by separator.
I want to display list of such objects in WPF listbox with grouping enabled. What I need is to have groups according to substrings.
Object1: Property = "string1;string2;string3"
Object2: Property = "string2;string3"
I expect listbox to be displayed like that:
string1
Object 1
Object 2
string2
Object 1
string3
Object 1
Object 2
Is that possible?
Thank you for your help.
Create a wrapper class.
class MyGroup
{
public string GroupID;
public object SomeObject;
}
Build your flat list of that wrapper class.
private List<MyGroup> BuildItemsSourceTogether()
{
List<MyGroup>itemsSource = new List<MyGroup>();
foreach(var obj in myListWithObjectsWhichPropertiesAreStringsWhichFuthermoreContainSubstrings)
{
var stringArray = obj.Property123.Split(';');
foreach(var str in stringArray)
{
itemsSource.Add(new MyGroup () { GroupID = str, SomeObject = obj});
}
}
return itemsSource;
}
Tell what property in your wrapper class should be used for grouping.
class Window1
{
public Window1()
{
InitalizeComponents();
var finalData = new ListCollectionView(BuildItemsSourceTogether());
finalData.GroupDescriptions.Add(new PropertyGroupDescription("GroupID"));
this.DataContext = finalData;
}
}
In XAML set the look of your groups and let WPF group that flat list of IDs for you.
I used a DataGrid with columns. You can use a ListBox.
<DataGrid ItemsSource="{Binding}">
<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.Columns>
<DataGridTextColumn Header="header1" Binding="{Binding SomeObject.Property321}" />
<DataGridTextColumn Header="header2" Binding="{Binding SomeObject.Property678}" />
</DataGrid.Columns>
</DataGrid>
Have fun.
I have a datagrid with the itemsource bound to a ListCollectionView with one group.
When i fill the collection, i want the first group autmatically viewed as expanded, how to code that in wpf (codebehind or mvvm)?
<DataGrid
ItemsSource="{Binding ResultColl}"
SelectedItem="{Binding Path=SelectedResultItem, Mode=TwoWay}"
SelectionMode="Single" IsReadOnly="True" >
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel>
<TextBox Text="{Binding Items[0].ID}" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=ID}"/>
<DataGridTextColumn Binding="{Binding Path=Typ}"/>
<DataGridTextColumn Binding="{Binding Path=Info}"/>
<DataGridTextColumn Binding="{Binding Path=orderDate, StringFormat={}{0:dd-MM-yyyy}}"/>
</DataGrid.Columns>
</DataGrid>
In the mvvm controller:
ListCollectionView tmp = new ListCollectionView(myList);
tmp.GroupDescriptions.Add(new PropertyGroupDescription("ID"));
ResultColl = tmp;
...
ListCollectionView _resultColl;
public ListCollectionView ResultColl
{
get { return _resultColl; }
set { _resultColl = value;
RaisePropertyChanged("ResultColl");
if (value != null && _resultColl.Count > 0)
SelectedResultItem = _resultColl.GetItemAt(0) as ItemResult;
}
}
When executing the code, the datagrid is filled the 1st item is selected but group is collapsed.
Add IsExpanded property to your class and add binding to Expander:
<Expander IsExpanded="{Binding Items[0].IsExpanded}">
Set IsExpanded for first to true
you can try add another bool property to your View Model defaulted to true but switching to false when first time used. And bind IsExpanded property of Expander to this with OneTime mode.
public bool IsExpanded
{
get
{
if (_isExpanded)
{
_isExpanded = false;
return true;
}
return false;
}
}
Xaml would be like that:
<Expander IsExpanded="{Binding DataContext.IsExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Mode=OneTime}">
I have DataGridComboBoxColumn in DataGrid:
<DataGridComboBoxColumn Header="{x:Static Properties:Resources.Unit}" Width="Auto"
SelectedValueBinding="{Binding UnitUnitId}"
SelectedValuePath="UnitId"
DisplayMemberPath="Name" >
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Units,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.Units,RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
and ComboBox:
<ComboBox x:Name="unitBox" ItemsSource="{Binding Path=Units}"
SelectedItem="{Binding TaxSubGroup.Unit}"
Grid.Row="2"
Grid.Column="1" Margin="11,11,0,0" HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static c:UnitToStringConverter.Default}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Units property:
public ObservableCollection<Unit> Units
{
get
{
return _units;
}
set
{
if(_units!=value)
{
_units = value;
RaisePropertyChanged("Units");
}
}
}
when i'm changing ComboBoxes value, DataGridComboBoxColumn's value updating automatically, but changing DataGrid's comboboxcolumn's value not updating ComboBoxes value. Why?
UPDATED
The problem was in DataGridComboBoxCulumn's SelectedValueBinding property. All i needed is to add UpdateSourceTrigger property:
SelectedValueBinding="{Binding UnitUnitId,UpdateSourceTrigger=PropertyChanged}"
Are you updating TaxSubGroup.Unit in the setter of UnitUnitId property?
In your DataContext model class do something similar to this...
public int UnitUnitId
{
get
{
return this.unitID;
}
set
{
this.unitID = value;
this.TaxSubGroup.Unit = Units.FirstOrDefault(u => u.UnitID == value);
this.NotifyPropertyChange("UnitUnitId");
}
}
This will match SelectedItem of Combobox by reference.