Bound ComboBoxItem not displaying - c#

I have a ComboBox used for selecting a file, and based on the user requiremenst the full path should be displayed in the ComboBox, whereas only the filename (minus directory) should be shown in the selectable items. I'm following the MVVM pattern, and the ComboBox is bound to an instance of type FileInfo in the ViewModel, where there is also an ObservableCollection<FileInfo> that becomes the ItemsSource. The current XAML for this is like so:
<ComboBox SelectedItem="{Binding FilePath}" ItemsSource="{Binding AvailableFiles}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Name="FilePathText" Text="{Binding FullName}" TextWrapping="WrapWithOverflow"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="False">
<Setter TargetName="FilePathText" Property="Text" Value="{Binding Name}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="FilePathText" Property="Text" Value="{Binding FullName}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The problem is, even though the default value for FilePath is a selectable item in the ItemsSource, and is bound properly (I've used Christian Moser's WPF Inspector to check the DataSource when the application starts), the ComboBox shows nothing until a value is selected. What is causing this? Since that ComboBoxItem's IsSelected property is null, it should be displaying the FullName of the FileInfo object.
Any help given would be greatly appreciated.

If SelectedItem is not contained within ItemsSource at the point of binding, then ComboBox has a nasty habit of setting SelectedItem back to null.
Try and hold off updating SelectedItem until ItemsSource is populated.

From what you are doing you may want to use SelectedValue and SelectedValuePath. This is what I did and it works for me.
I did this in code but this should still work with Binding.
In C#:
availableFiles.Add(new FileInfo(#"Program.cs"));
filebox.ItemsSource = availableFiles;
filebox.SelectedValue = new FileInfo(#"Program.cs");
filebox.SelectedValuePath = "FullName";
and my combobox is template
<ComboBox Name="filebox">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Name="FilePathText" Text="{Binding FullName}" TextWrapping="WrapWithOverflow"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="False">
<Setter TargetName="FilePathText" Property="Text" Value="{Binding Name}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="FilePathText" Property="Text" Value="{Binding FullName}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In your case
<ComboBox Name="filebox" SelectedValue="{Binding FileInfoObject}" SelectedValuePath ="FullName">
Update This works
<ComboBox Name="filebox" SelectedValuePath="Name" SelectedItem="{Binding FileInfoObject}" ItemsSource="{Binding AvailableFiles}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Name="FilePathText" TextWrapping="WrapWithOverflow"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="False">
<Setter TargetName="FilePathText" Property="Text" Value="{Binding Name}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="True">
<Setter TargetName="FilePathText" Property="Text" Value="{Binding FullName}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="{x:Null}">
<Setter TargetName="FilePathText" Property="Text" Value="{Binding FullName}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>

Related

WPF DataGrid trigger on other cell value

I have DataGrid:
<DataGrid x:Name="PART_DataGrid"
ItemsSource="{TemplateBinding Items}"
AutoGenerateColumns="False"
Margin="2,25,2,2">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Language" Width="SizeToCells" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Path=Language, Mode=OneWay}"
ItemsSource="{Binding LanguagesSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
FontSize="16"
IsEnabled="False">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=test }" Value="">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Translation" Width="SizeToCells" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="TranslationValueTB"
Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}"
BorderThickness="0"
FontSize="16" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
And I want to set that first column ComboBox will be enabled when second TextBox.Text is empty, I have tried different binding methods but none of them have worked. So how can I do that?
Bind to the Value source property that the TextBox in the second column is bound to:
<DataGridTemplateColumn Header="Language" Width="SizeToCells" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Path=Language, Mode=OneWay}"
ItemsSource="{Binding LanguagesSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
FontSize="16"
IsEnabled="False">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Value}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding Value}" Value="">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Also make sure that the class where the Value property is defined implements the INotifyPropertyChanged interface and raises the PropertyChanged event in the setter of the Value property.
Remove the IsEnabled = false from combobox and do the same in the setter as follows, and do the binding as like follows,
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=Item.Name }" Value="{x:Static sys:String.Empty}">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
Item.Name - > this is the property you need to check

How to bind with a relative source that is Ancestor and Self?

I have the following code:
<ItemsControl ItemsSource="{Binding MyDictionary}" >
<Image Width="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Value}" Value="False">
<Setter Property="Source" Value="{Binding RelativeSource =
{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}},
Path=DataContext.SecDic}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Value}" Value="True">
<Setter Property="Source" Value="{Binding RelativeSource =
{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}},
Path=DataContext.ThirdDic}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</ItemsControl>
MyDictionary - Is a dictionary that the key is of type enum
Now This binding works if I change the SecDic and ThirdDic from dictionaries to BitmapImages. But I want that for each key (enum) in MyDictionary there will be a different True/False image.
So I tried to write:
<Setter Property="Source" Value="{Binding Path=DataContext.SecDic[Key],
RelativeSource={AncestorType={x:ItemsControl}}}"/>
But it doesn't work since the "Key" belongs to the data context of the image (of MyDictionary) and I changed the relative source to be the items control so there is no "Key".
Any suggestions?
What I did to solve this is a multibinding + a custom multibinding converter.
There was one binding for the dictionary and one for the key.

changing binding based on datatrigger property

I'm trying to change the text of a textblock based on a datatrigger. The datatrigger looks like this:
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowFileExtensionsProperty}" Value="true">
<Setter Property="Text" Value="{Binding Path=Name}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=ShowFileExtensionsProperty}" Value="false">
<Setter Property="Text" Value="{Binding Path=NameWithoutExtension}" />
</DataTrigger>
</Style.Triggers>
</Style>
This code is found within the xaml file of the class "MyView" The cs file of this class contains the following code:
public static readonly DependencyProperty ShowFileExtensionsProperty =
DependencyProperty.Register("ShowFileExtensions", typeof(bool), typeof(MyView), new UIPropertyMetadata(false));
public bool ShowFileExtensions
{
get { return (bool)GetValue(ShowFileExtensionsProperty); }
set
{
SetValue(ShowFileExtensionsProperty, value);
updateViewCollection();
}
}
The textblock is displayed within a listview, that has been bound to a list of objects, these objects contain the property "Name" and "NameWithoutExtension".
The problem is that the text in the textblock always stays blank. Somehow the datatrigger is never triggered. Any idea what is wrong with the datatriggers?
PS: The full code follows
<UserControl x:Class="com.xploreplus.Filedrops.explorer.listview.FiledropsFileList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:l="clr-namespace:com.xploreplus.Filedrops.explorer.listview.views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style x:Key="Extension" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowFileExtensionsProperty, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" Value="true">
<Setter Property="Text" Value="brol2" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=ShowFileExtensionsProperty, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" Value="false">
<Setter Property="Text" Value="blabla" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<ListView Name="viewComponent">
<ListView.View>
<GridView>
<GridViewColumn Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Source="{Binding Path=Icon}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource Extension}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Extension" DisplayMemberBinding="{Binding Path=Extension}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
As the textblocks are shown in listview then the DataContext of textblock will the ItemDataContext if not set explicitly. That is why your bindings are not working. You will have to provide the relativesource to binding
Try updating the bindings like
<DataTrigger Binding="{Binding Path=ShowFileExtensions, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" Value="true">
used {x:Type Control} assuming this is the type of your view else you will have to change to your view type.
Hope this helps
Thanks

Make xaml datatrigger work in c# code

I am new to xaml, i have following code below, my question is how can i call InvalidForeground from c# code to change the color of checkbox text?
<ControlTemplate x:Key="ItemTemplate"
TargetType="ListViewItem">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="CkBoxVisual">
<CheckBox.IsChecked>
<Binding Path="IsSelected"
Mode="TwoWay">
<Binding.RelativeSource>
<RelativeSource Mode="TemplatedParent" />
</Binding.RelativeSource>
</Binding>
</CheckBox.IsChecked>
<DataTrigger Binding="{Binding InvalidForeground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Value="true">
<Setter TargetName="CkBoxVisual" Property="Foreground" Value="Red"/>
</DataTrigger>
</CheckBox>
<ContentPresenter />
</StackPanel>
</ControlTemplate>
In your code, you are not calling anything. In it, you're hoping a change of dependency property..
But a control of type Window does not have a dependency property with the name: "InvalidForeground".
This trigger will never be triggered.
What is your goal, change a property or be notified of a change (trigger)?
Edit: There are a number of rules you must follow:
1) The control referenced in Binding property from DataTrigger (RelativeSource):
Binding="{Binding Path=InvalidForeground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Must have a dependency property called "InvalidForeground", which works correctly, based on this, will not work:
{x:Type Window}
its type must be declared, will work, for example:
{x:Type my:ControlName}
2) A property changed by a Trigger or DataTrigger can never be explicitly stated, for example. Will not work:
<TextBlock Text="{Binding Any}" Foreground="#FFCCCCCC">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem, Mode=FindAncestor}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Will work:
<TextBlock Text="{Binding Any}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#FFCCCCCC"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem, Mode=FindAncestor}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
3) Check the operation of Bindings using RelativeSource they have some peculiarities (you should know a little about the visual tree of WPF).
http://msdn.microsoft.com/en-us/library/ms743599.aspx
4) You should check that the binding is working properly and can follow these steps:
http://www.codeproject.com/Articles/244107/Debugging-WPF-data-bindings

ValidationRules in DataGrid

I am populating a DataGrid with a DataSet.
I have my dataGrid enabled for validationRules by make in each DataGridColumn ValidatesOnDataError=True but when I added a DataGridRow that has DataErrors then the DataGridRow is blocked and I cannot do anything else neither correct the DataGridRow
<!--Template for DataGridRow in Error state-->
<ControlTemplate x:Key="BasicRowValidationErrorTemplate">
<Grid Margin="0,-2,0,-2" ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}},
Path=(Validation.Errors)[0].ErrorContent}">
<Ellipse StrokeThickness="0" Fill="Red" Width="{TemplateBinding FontSize}" Height="{TemplateBinding FontSize}" />
<TextBlock Text="!" FontSize="{TemplateBinding FontSize}" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>
<!--Style for DataGridCell in static mode-->
<Style x:Key="textBlockInError" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<!--Style for DataGridCell in edit mode-->
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
And I have defined my Columns like
<DataGrid>
<DataGrid.RowValidationRules>
<DataErrorValidationRule ValidationStep="ComittedValue"/>
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridTextColumn Header="Nombre" Binding="{Binding Nombre, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" ElementStyle="{StaticResource textBlockInError}"
EditingElementStyle="{StaticResource textBoxInError}"/>
<DataGridTemplateColumn Header="Price">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Price, ValidatesOnDataErrors=True}" Style="{StaticResource textBlockInError}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl},AncestorLevel=2},
Path=DataContext.Prices}" DisplayMemberPath="DisplayMember"
SelectedValue="{Binding Price}" IsEditable="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}, AncestorLevel=2},
Path=DataContext.IsComboBoxEditable, Mode=OneWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have in my ViewModel
DataTable Price;
DataTable Prices;
Price has two columns Name and Price
and Prices has two columns DisplayMember and ValueMember
I get the Precios from a file, if the file is not loaded then I do not have precies to show so I made Editable my combobox when i do not have the file. And just set my selected value with the one that user wrote
private void ComboBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
if (comboBox.IsEditable)
{
comboBox.SelectedValue = (sender as ComboBox).Text;
}
}
Why is blocked my DataGridRow ?

Categories