WPF ComboBox selected item text - wpf

I made a custom combobox where I have a TextBlock (named mySelectedContent) to display the selected item and a TextBox for editing in "IsEditable" mode. I have a MultiDataTrigger that is being shot correctly, however, I am unable to "catch" the text of the selected item and put it into the TextBlock. How should be mounted the correct expression in place of "???". Thanks a lot!
Here is the code of the trigger (I'm showing mainly the part of the trigger because it's just in it the problem):
<ComboBox.Resources>
<Style x:Key="myComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<ToggleButton>
...
</ToggleButton>
<TextBlock
Name="mySelectedContent"
.../>
<TextBox x:Name="myEditableTextBox"
.../>
<Popup>
...
</Popup>
</Grid>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
...
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter TargetName="myEditableTextBox" Property="Visibility" Value="Hidden"/>
<Setter TargetName="mySelectedContent" Property="Visibility" Value="Visible"/>
<Setter TargetName="mySelectedContent" Property="Text" Value="???"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Resources>

It was solved with cYounes first suggestion. I used:
Value={Binding ElementName=MyEditableTextBox Path=Text}
and it works as expected!
Thanks!

That's way too much work when you simply could have used the Tag property to get the value easily with 2 lines:
in XAML:
<ComboBoxItem Content="This Value" Tag="This Value"/>
Then:
GetValue=ComboBoxName.SelectedItem.Tag.ToString()
will give you "This Value" and not
"System.Windows.Controls.ComboBoxItem: This Value"
Much simpler, faster and less time consuming.

Related

WPF ControlTemplate Height

I have the following style for validating input in my controls:
<Style x:Key="MyErrorTemplate" TargetType="Control">
<Style.Setters>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="ControlErrorTemplate">
<StackPanel Orientation="Vertical" Height="Auto">
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder x:Name="Holder"/>
</StackPanel>
<Label Foreground="Red" Content="{Binding ElementName=Holder,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
If an error happens, the error message in the label appears under the control (e.g. textbox) and overlaps the control below. I made StackPanel's Height="Auto", but it didn't help. Each control is in a Grid cell, and the Grid's row Height is also Auto.
Could you please tell me what I am missing? I want the error message to push what is below down.
Thanks.
Validation.ErrorTemplate shows error feedback on an adorner layer. This means all controls in this template will not be considered when the layout system is measuring and arranging the controls on the adorned element layer.
I found this and thanks LPL, i did not know that about the adorner layer.
My solution was a margin "hack". I just used the trigger:
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="Margin" Value="0,0,0,28"/>
</Trigger>
</Style.Triggers>
To increase the bottom margin of the adorned textbox. I set the margin large enough to make room for a single string textblock/label and then the content below was moved down

WPF Style Validation Trigger Command

I have some dynamic generated Textboxes with Validators. I want them to send a Command to VM, if a validation error occurs. This Behavior is placed in a style, so I don't need to write it into the xaml generation.
Here's the Code:
<behaviors:Triggers x:Key="validationTrigger" x:Shared="False">
<behaviors:ValidationErrorEventTrigger>
<cmd:EventToCommand Command="{Binding ValidationError,NotifyOnValidationError=True}"
PassEventArgsToCommand="True" />
</behaviors:ValidationErrorEventTrigger>
</behaviors:Triggers>
<Style x:Key="EditableTextBox" TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="#DDFFDD" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="behaviors:OCCInteraction.Triggers" Value="{StaticResource ResourceKey=validationTrigger}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Background" Value="#FFDDDD"/>
</Trigger>
</Style.Triggers>
</Style>
The Problem I'm having now, is that the "Onvalidation" Event is called in the VlidationErrorEventTrigger class, but the Command isn't called in the Viewmodel.
I've tested it with a direct integration and not with a style and it works this way.
So maybe it has something to do with the Binding of the Command or so...
I hope this description is enough to solve the Problem. If not please tell me :)
I am not sure how you binding is done but assuming that DataContext of you `Control/Window has your ViewModel Instance andValidationError` is your command...
<behaviors:Triggers x:Key="validationTrigger" x:Shared="False">
<behaviors:ValidationErrorEventTrigger>
<cmd:EventToCommand Command="{Binding Path=DataContext.ValidationError,RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}",NotifyOnValidationError=True}"
PassEventArgsToCommand="True" />
</behaviors:ValidationErrorEventTrigger>
</behaviors:Triggers>
This is assuming that your trigger is not able to find the command required.

Can I get this binding into a style?

I would like to take the xaml I currently have for a ComboBox (below), and condense it into something like the Style also shown below. I think this should work, but I have a 'Type'ing issue and not sure how to resolve it
"Cannot resolve the Style Property 'Margin'. Verify that the owning type is the Style's TargetType, or use Class.Property syntax to specify the Property.)
As I look at the existing ComboBoxStyle (also below) that I'm looking to base this new style off of, I see that I hadn't used x:Type, but it does seem to work.
Is there any reason this new style shouldn't work? What must I change?
Cheers,
Berryl
combo box, as is, working):
<ComboBox
x:Name="cboDepartmentFilter" Style="{StaticResource ComboBoxStyle}"
Margin="{StaticResource FliterPanelItem_Margin}" Width="{StaticResource FilterPanelItem_Width}"
ItemsSource="{Binding Path=DepartmentFilterControl.Choices}"
ToolTip="{Binding DepartmentFilterControlData.ToolTipTitle}"
/>
what I want:
<ComboBox Style="{StaticResource FilterPanelComboBoxStyle}" DataContext="{Binding DepartmentFilterControl}" />
<!- in some resource file ->
<Style x:Key="FilterPanelComboBoxStyle" BasedOn="{StaticResource ComboBoxStyle}">
<Setter Property="Margin" Value="{StaticResource FliterPanelItem_Margin}" />
<Setter Property="Width" Value="{StaticResource FilterPanelItem_Width}" />
<Setter Property="ItemsSource" Value="{Binding Choices}" />
<Setter Property="ToolTip" Value="{Binding ToolTipTitle}" />
</Style>
<!--
This style defines a common margin for items in a filter panel.
-->
150
existing ComboBoxStyle:
<!-- ComboBox Style -->
<Style x:Key="ComboBoxStyle" TargetType="ComboBox">
<Setter Property="Background" Value="{StaticResource headerBrush}" />
...
<Setter Property="Template">
<Setter.Value>
...
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle" Value="{StaticResource ComboBoxItemStyle}" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
You still need to specify the TargetType in the derived style. (Or you prefix the properties with "ComboBox.")

WPF ComboBox that shows nothing selected when disabled (IsEnabled == false)

I'm thinking out different ways to have a WPF ComboBox show blank as if nothing is selected when IsEnabled is set to false. Like always I'm trying to do this without having to redefine the whole control template for the ComboBox which is always a struggle I have with WPF. If anybody has any solutions more elegant than redefining the whole ComboBox control template please let me know.
The reason for what I'm trying to do is I have a CheckBox that represents an "All" option and when checked it disables the ComboBox which is used to pick only a single individual item. If my CheckBox is checked it is sometimes confusing to the users to see a value remaining in the ComboBox since that value has no meaning in that state of the UI.
Another requirement is that the solution cannot modify the SelectedValue, SelectedIndex, or SelectedItem values of the ComboBox since I would like to retain the previuosly selected item in the case that the users unchecks the "All" CheckBox.
Solution based on HCL's answer:
<ComboBox IsEnabled="{Binding ElementName=myCheckBox, Path=IsChecked}"
ItemsSource="{Binding Path=MyItems}"
SelectedValue="{Binding Path=MySelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="content" Content="{Binding MyItemDescription}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}, Path=IsEnabled}"
Value="False">
<Setter TargetName="content"
Property="Visibility"
Value="Hidden" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You can do something with triggers:
Try setting the ItemTemplate to an empty DataTemplate when the box is disabled. This will affect the rendering of the selected item and therefore hide it.
Another simple but not very nice solution would be to set the foreground color to the same as a background color.
I believe you can do this with a Style, rather than redefining the control template. Use a Trigger on the IsEnabled property to set the text shown in the ComboBox. Altering the SelectedItem would be my first approach, but since you don't want to do that, you may find success setting the DisplayMemberPath. Something like this (untested)...
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Trigger.Setters>
<Setter Property="DisplayMemberPath" Value="{x:Null}"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
Here's a style that does what you want. It employs a technique that I use all the time: a grid that contains multiple versions of the control, and data triggers that ensure that only one version is visible at any one time.
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<DockPanel>
<CheckBox x:Name="IsActive" DockPanel.Dock="Left"/>
<Grid>
<ComboBox
ItemsSource="{TemplateBinding ItemsSource}"
SelectedItem="{TemplateBinding SelectedItem}"
SelectedIndex="{TemplateBinding SelectedIndex}"
SelectedValue="{TemplateBinding SelectedValue}">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=IsActive, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
<ComboBox>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=IsActive, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</Grid>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
This preserves the selected item, selected index, and selected value, just as you want. In fact, it does this a little too well; there's not actually a way of telling that the user deactivated the combo box, since there's no property on ComboBox that exposes this information. I'd probably actually implement this as a custom control derived from ComboBox that exposed the value of the check box as an IsActive property. There are lots of other ways to do it.

WPF: How to make empty TextBlock not to occupy space?

Let's say that I have a simple layout such as this:
<StackPanel>
<TextBlock Text="{Binding Path=Title}" />
<TextBlock Text="{Binding Path=ShortDescription}" />
<TextBlock Text="{Binding Path=LongDescription}" />
</StackPanel>
Now when I have ShortDescription set to null or empty string there's still a gap in place of second TextBlock. Is there some property to prevent an empty textblock from occupying space? Or should I use some other control?
Thanks.
You want to set the visibility of the textbox to "Collapsed".
Visibility can be either:
Visible - Self explanatory
Hidden - Invisible but still takes up space
Collapsed - Invisible and takes up no space
Edit: You should probably set up a trigger, like so:
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
You may want to try this:
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
This should fix the empty space issue based on a Null / Empty Binding.

Resources