How to trim text displayed in a ComboBox? - c#

I have my own combobox (autocompleteCombobox) where I would like to see only 35 characters of the selectedItem, but with a tooltip that show the full name.
The user control code:
<UserControl.Resources>
<Style x:Key="ComboboxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding ShownName}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<ComboBox x:Name="Combobox" IsSynchronizedWithCurrentItem="True"
IsEditable="True" TextSearch.Text="ShownName"
IsTextSearchEnabled="True" DisplayMemberPath="ShownName"
ToolTip="{Binding SelectedItem.ShownName,ElementName=autoComplete}"
ItemsSource="{Binding ItemsSource,ElementName=autoComplete}"
SelectedItem="{Binding SelectedItem, ElementName=autoComplete}"
Style="{StaticResource ComboboxStyle}">
<ComboBox.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding Path=SelectItemCommand, ElementName=autoComplete}"
CommandParameter="ShownName"/>
</ComboBox.InputBindings>
</ComboBox>
</Grid>
And inside the cs file of the autocompletecombobox:
public static readonly DependencyProperty MaxTextLengthProperty =
DependencyProperty.Register(
"MaxTextLength",
typeof(int),
typeof(ComboBoxAutoComplete),
new UIPropertyMetadata(35));
public int MaxTextLength
{
get { return (int)GetValue(MaxTextLengthProperty); }
set
{
SetValue(MaxTextLengthProperty, value);
LimitTextInCombobox();
}
}
private void LimitTextInCombobox()
{
Combobox.Text = Combobox.Text.Substring(0, MaxTextLength);
}
But it doesn't work...

Rather than trimming the text to a certain number of characters you could let WPF trim it for you with respect to the visual width of the text, which probably would look better. If that is an option for you, you could look into the TextBlock.TextTrimming-property.

you can use converter
[ValueConversion(typeof(object), typeof(string))]
public class StringFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
string str = value.ToString();
if (string.IsNullOrEmpty(str))
{
return "";
}
else
{
if (str.Length >= 35)
{
return str.Substring(0, 35);
}
else
{
return str;
}
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
and in Xaml
<Windows.Resources>
<dict:StringFormatConverter x:Key="StringFormatConverter"/>
</Windows.Resources>
<UserControl.Resources>
<Style x:Key="ComboboxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding ShowName, Converter={StaticResource StringFormatConverter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>

The setter of a CLR wrapper for a dependency property should always only call the SetValue method to set the value of the dependency property. Nothing else:
public int MaxTextLength
{
get { return (int)GetValue(MaxTextLengthProperty); }
set
{
SetValue(MaxTextLengthProperty, value);
}
}
Also, you want to keep the original value to be able to display it in the tooltip anyway.
Using a converter as suggested by #Alematt seems like a good option. Just modify the ItemTemplate slightly:
<UserControl.Resources>
<local:Converter x:Key="converter" />
<Style x:Key="ComboboxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding ShownName, Converter={StaticResource converter}}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
And create a converter class:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string shownName = value as string;
if (!string.IsNullOrEmpty(shownName) && shownName.Length > 35)
return shownName.Substring(0, 35) + "...";
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
And keep the Tooltip as it is:
ToolTip="{Binding SelectedItem.ShownName, ElementName=autoComplete}"

Instead of that just pass the SelectedItem to your converter like so:
<ComboBox x:Name="cmb">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource multi}">
<Binding Path="."/>
<Binding Path="SelectedItem" ElementName="cmb"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Style>
<ComboBox.Items>
<sys:String>Very long string 1</sys:String>
<sys:String>Very long string 2</sys:String>
</ComboBox.Items>
</ComboBox>
Then use your converter like so:
class MultiValConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] != null && values[1] != null)
{
if (values[0].ToString() == values[1].ToString())
{
return "...";//put your logic here i.e. substring(0,30);
}
}
return values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and you would reference the converter like this:
<locals:MultiValConverter x:Key="multi"/>

Related

Cross-binding checkboxes - isChecked #1 -> isEnabled #2

I have a ListBox that takes boolean elements from list in my model and represent them as checkboxes. Just after building project the second checkbox isEnabled is set to false. If I modify (eg. cut and paste same converter) binding in the second checkbox in debug, the binding start working correctly. Also I have a global checkboxes that modyfi isChecked properties of all checkboxes from listBox. If I set globalCheckbox #2, all listBox_checkBoxes #2 are set to true and all listBox_checkBoxes #1 isEnabled property are set to false
XAML:
<ListBox x:Name="ListBox_assent" SelectedIndex="-1" Grid.Row="2" ItemsSource="{Binding Path=FullDataAssetList.List}" IsSynchronizedWithCurrentItem="True" Height="Auto">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource MaterialDesignListBoxItem}">
<Setter Property="Margin" Value="2"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Focusable" Value="False" />
<EventSetter Event="RequestBringIntoView" Handler="ListBoxItem_RequestBringIntoView"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Grid.Row="0" Opacity="{Binding Path=SkipAssentTemp, Converter={StaticResource BoolToOpacity}}">
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="3">
<CheckBox x:Name="chbx_Assent" HorizontalContentAlignment="Left" FlowDirection="RightToLeft" ToolTip="Skip" IsChecked="{Binding SkipAssent, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding SkipAssentTemp, Converter={StaticResource InverseBoolean}}" LostFocus="chbx_Assent_LostFocus" Background="#FFCB0000"/>
<TextBlock FontSize="16" Text=" / " VerticalAlignment="Center"/>
<CheckBox x:Name="chbx_AssentTemp" HorizontalContentAlignment="Left" FlowDirection="RightToLeft" ToolTip="Skip temp." IsChecked="{Binding SkipAssentTemp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding SkipAssent, Converter={StaticResource InverseBoolean}}" LostFocus="chbx_AssentTemp_LostFocus" Background="#FFCBA300"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Converter:
public class InverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType == typeof(bool) || targetType == typeof(bool?))
{
if ((bool?)value == true)
{
return false;
}
else
if ((bool?)value == false)
{
return true;
}
return null;
}
else
{
throw new InvalidOperationException("The target must be a boolean");
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
How can i fix binding to get full functionality just after building?
I have found solution. DataTrigger in xaml was overwritnig isEnabled property

Access element from style WPF

I have a style declared in a ResourceDictionary like so.
<Style x:Key="MapMarkerLabelStyle" TargetType="{x:Type TextBlock}">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding ActualWidth,
Converter={StaticResource DoubleMultiplierConverter},
ConverterParameter=-0.5}"
Y="-62"></TranslateTransform>
</Setter.Value>
</Setter>
</Style>
And the textblock
<TextBlock Style="{StaticResource MapMarkerLabelStyle}" />
And the converter:
public class DoubleMultiplierConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var originalValue = (double) value;
var format = new NumberFormatInfo {NumberDecimalSeparator = "."};
var multiplier = System.Convert.ToDouble(parameter, format);
return originalValue * multiplier;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In the translate transform i need to access the textblock ActualWidth property but cant figure out how the binding should look.
The Binding is missing an appropriate source object. To bind to the TextBlock's ActualWidth property, you should set the RelativeSource like this:
<Style x:Key="MapMarkerLabelStyle" TargetType="TextBlock">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform
X="{Binding Path=ActualWidth,
RelativeSource={RelativeSource AncestorType=TextBlock}
Converter={StaticResource DoubleMultiplierConverter},
ConverterParameter=-0.5}"
Y="-62"/>
</Setter.Value>
</Setter>
</Style>
Try this.
<TranslateTransform X="{Binding RelativeSource={RelativeSource Self},
Path=ActualWidth,
Converter={StaticResource DoubleMultiplierConverter},
ConverterParameter=-0.5}"
Y="-62">
</TranslateTransform>

WPF TemplateBinding Concatenate String for Value

I want to add a Custom Property (via a Dependency Property) to my custom ToggleButton Template.
Now i want to have the value of the containing Label (as a placeholder for future implementation) to be a concatenated value of, say "Hello " and the actual Property Value.
Without the concatenation it works fine (displaying "Warrior" on the Label)
But when i try to set the label as a concatenation the xaml doesnt compile anymore.
<Label Content="Hello {TemplateBinding local:HeroClassCheckbox.HeroClass}"/>
How can i achieve this?
The Rest of the Code:
My .xaml
<Window.Resources>
<ResourceDictionary>
<Style x:Key="HeroClassCheckbox" TargetType="ToggleButton">
<Setter Property="Content" Value="Green" />
<Setter Property="local:HeroClassCheckbox.HeroClass" Value="NoClass"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Label Content="{TemplateBinding local:HeroClassCheckbox.HeroClass}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ToggleButton Width="150" Height="50" local:HeroClassCheckbox.HeroClass="Warrior" Style="{DynamicResource HeroClassCheckbox}"/>
</Grid>
My .xaml.cs
public static class HeroClassCheckbox
{
public static readonly DependencyProperty HeroClassProperty = DependencyProperty.RegisterAttached("HeroClass",
typeof(string), typeof(HeroClassCheckbox), new FrameworkPropertyMetadata(null));
public static string GetHeroClass(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
return (string)element.GetValue(HeroClassProperty);
}
public static void SetHeroClass(UIElement element, string value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(HeroClassProperty, value);
}
}
You should use a converter as an approach to your goal. Here is an example..
public class HelloLabelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
char[] removeThis = "Hello ".ToCharArray();
return value.ToString().TrimStart(removeThis);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return string.Format("Hello {0}", value);
}
}
<Window.Resources>
<local:HelloLabelConverter x:Key="HelloLabelConverter" />
</Window.Resources>
<Grid>
<Label Content="{Binding ElementName= lblPropertyToBind, Path=Text, Converter={StaticResource HelloLabelConverter}}"></Label>
</Grid>

WPF - Disable tooltip when value is null or empty

I have this problem, i'm using telerik on WPF and a source on my grid and doing the correct bindings. I want to be able to disable the tooltip when the value of the binding is null or empty. How can it be done?
This is one example:
<telerik:GridViewDataColumn x:Name="GRIDVIEWCOLUMN_ENDDATE" Header="Data de Conclusão" DataMemberBinding="{Binding ClosedDate, StringFormat=dd-MM-yyyy}" IsVisible="False" Width="auto" IsFilterable="False">
<telerik:GridViewDataColumn.ToolTipTemplate>
<DataTemplate>
<TextBlock Text="{Binding ClosedDate, StringFormat=dd-MM-yyyy}" FontFamily="Segoe UI Light" FontSize="13.667" />
</DataTemplate>
</telerik:GridViewDataColumn.ToolTipTemplate>
</telerik:GridViewDataColumn>
Just bind the Visibility of your tooltip (in this case, you have provided a TextBlock) to the same property - ClosedDate, and use a converter to get the value based on your logic.
public class TooltipVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (value is string)
{
return String.IsNullOrEmpty(value as string) ? Visibility.Collapsed :Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In your XAML, you would have something like this. You declare your converter as a static resource somewhere in your view, and later refer to it in your binding.
<converters:TooltipVisibilityConverter x:Key="TooltipVisibilityConverter"/>
<telerik:GridViewDataColumn x:Name="GRIDVIEWCOLUMN_ENDDATE" Header="Data de Conclusão" DataMemberBinding="{Binding ClosedDate, StringFormat=dd-MM-yyyy}" IsVisible="False" Width="auto" IsFilterable="False">
<telerik:GridViewDataColumn.ToolTipTemplate>
<DataTemplate>
<TextBlock Text="{Binding ClosedDate, StringFormat=dd-MM-yyyy}" FontFamily="Segoe UI Light" FontSize="13.667" Visibility="{Binding ClosedDate, Converter={StaticResource x:Key="TooltipVisibilityConverter"}" />
</DataTemplate>
</telerik:GridViewDataColumn.ToolTipTemplate>
I know this is a pretty old question but I was trying to hide empty tooltips and found a much simpler way to do so here: http://wpfthoughts.blogspot.com/2014/02/hiding-empty-tooltips.html.
Basically if you put a resource dictionary in App.xaml you can automatically hide all empty/null tooltips in your application.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style TargetType="{x:Type ToolTip}">
<Style.Triggers>
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
<Trigger Property="Content" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
It is overkill if you only need it in one spot but it's a super simple fix that prevents you from having to add code to every page that has tooltips. Hopefully this helps someone out.
You can consider using a IValueConverter to show/hide the tooltip border
<telerik:GridViewDataColumn x:Name="GRIDVIEWCOLUMN_ENDDATE" Header="Data de Conclusão" DataMemberBinding="{Binding ClosedDate, StringFormat=dd-MM-yyyy}" IsVisible="False" Width="auto" IsFilterable="False">
<telerik:GridViewDataColumn.ToolTipTemplate>
<DataTemplate>
<Border Background="Black" Visibility="{Binding ClosedDate, Converter={StaticResource BorderVisible}}" >
<TextBlock Text="{Binding ClosedDate, StringFormat=dd-MM-yyyy}" FontFamily="Segoe UI Light" FontSize="13.667" />
</Border>
</DataTemplate>
</telerik:GridViewDataColumn.ToolTipTemplate>
</telerik:GridViewDataColumn>
class BorderVisibilitySetter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//check if the control's content property is null or empty
if(value == null || value.ToString() == string.Empty)
return Visibility.Collapsed;
else
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Setting the tooltip parent control IsEnabled to false will effectively prevent the tooltip to pop unless ToolTipService.ShowOnDisabled is set to true.
Check george.zakaryan's answer, but instead of using a converter to bind the visibility of the tooltip textblock to its content, use a similar converter to Bind the IsEnabled property of the tooltip's parent to its Textblock's text.
public class StringToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string)
{
return String.IsNullOrEmpty(value as string) ? false : true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
in your xaml
<UserControl.Resources>
<utils:StringToBoolConverter x:Key="StringToBoolConverter"/>
</UserControl.Resources>
and
<TextBlock Text="{Binding SrcDrive, Mode=OneWay}" IsEnabled="{Binding SrcDrive, Converter={StaticResource StringToBoolConverter}, Mode=OneWay}" Width="400" >
<TextBlock.ToolTip>
<TextBlock Text="{Binding SrcDrive, Mode=OneWay}" />
</TextBlock.ToolTip>
</TextBlock>
Native Telerik tooltip for GridView has a defect, which is even when you set Visibility=Collapsed it will still show an empty box (see below image):
Code:
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding Name}">
<telerik:GridViewColumn.ToolTipTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Visibility="Collapsed" />
</DataTemplate>
</telerik:GridViewColumn.ToolTipTemplate>
</telerik:GridViewDataColumn>
</telerik:RadGridView.Columns>
Result:
Solution:
Instead of using null check Converter, the approach that worked for me was to skip showing the tooltip when TextBlock.ToolTipOpeningEvent is triggering.
To do so in your xaml.cs do the following:
public partial class MainWindow: Window
{
public MainWindow()
{
InitializeComponent();
EventManager.RegisterClassHandler(typeof(GridViewCell),
TextBlock.ToolTipOpeningEvent,
new RoutedEventHandler(OnToolTipOpening));
}
private void OnToolTipOpening(object sender, RoutedEventArgs e)
{
if (sender is GridViewCell cell) // show tooltip only when text is trimmed
e.Handled = !IsTextTrimmed(cell);
}
static bool IsTextTrimmed(GridViewCell cell) => cell?.Value.ToString().Length > 50;
}
That's it, and no need to write any additional logic to show/hide tooltips.

WPF Value Converter to replace error code with localized string

I have a viewmodel implementing the IDataErrorInfo interface for validation, for an error it produces a string which is a key in the resource dictionary for a localized string describing the error. However when trying to apply the following style and template to the textbox I get the red border but no tooltip, however removing my converter and using the default one gives me the tooltip but obviously not the localized string.
Can you see what I am doing wrong and/or if there is a better way of doing this?
class MessageCodeToMessageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
string messageCode = (string)value;
try
{
return (string)App.Current.Resources[messageCode];
}
catch (Exception)
{
return messageCode;
}
}
else
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "";
}
}
<local:MessageCodeToMessageConverter x:Key="Converter"></local:MessageCodeToMessageConverter>
<Style x:Key="TextBox" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent, Converter={StaticResource Converter}}"/>
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="ErrorTemplate">
<DockPanel LastChildFill="True">
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
In my opinion if your resource key is defined in the App.xaml then it should work. But your resource key is probably in resource in some user control. (string)App.Current.Resources[messageCode]; search only in the App.xaml resource.
Solution for you can be use multivalueconverter
class MessageCodeToMessageConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetObject = values[0] as FrameworkElement;
if (targetObject == null)
{
return DependencyProperty.UnsetValue;
}
if (value != null)
{
string messageCode = (string)values[1];
try
{
return targetObject.FindResource(messageCode);
}
catch (Exception)
{
return messageCode;
}
}
else
{
return null;
}
}
public object ConvertBack(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "";
}
}
and
<Style x:Key="TextBox" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{StaticResource Converter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}" />
<Binding RelativeSource="{x:Static RelativeSource.Self}" Path="(Validation.Errors)[0].ErrorContent" />
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>

Categories

Resources