Remove white space from MultiBinding StringFormat - wpf

Unable to remove whitespace from StringFormat.
If no 'Type' data the following whitespace doesn't fallback.
ReleaseDate, space, space, dot, space, space, Type, space (some reason it doesn't remove this space), Color
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0:yyyy} · {1} {2}" >
<Binding Path="ReleaseDate" FallbackValue="{x:Null}"/>
<Binding Path="Type" FallbackValue="{x:Null}"/>
<Binding Path="Color" FallbackValue="{x:Null}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>

You may probably replace the MultiBinding and its StringFormat by a DataTrigger:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text">
<Setter.Value>
<MultiBinding StringFormat="{}{0:yyyy} · {1} {2}" >
<Binding Path="ReleaseDate" FallbackValue="{x:Null}"/>
<Binding Path="Type"/>
<Binding Path="Color" FallbackValue="{x:Null}"/>
</MultiBinding>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="{x:Null}">
<Setter Property="Text">
<Setter.Value>
<MultiBinding StringFormat="{}{0:yyyy} · {1}" >
<Binding Path="ReleaseDate" FallbackValue="{x:Null}"/>
<Binding Path="Color" FallbackValue="{x:Null}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

While the approach with DataTrigger works, it is very verbose. I would make it a two-step process: bind Type separately with a StringFormat which has a space, then bind everything together
<TextBlock Name="Title"
local:Attached.SomeString="{Binding Type,
StringFormat='\{0} ',
TargetNullValue=''}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0:yyyy} · {1}{2}">
<Binding Path="Login" />
<Binding Path="(local:Attached.SomeString)"
RelativeSource="{RelativeSource Self}" />
<Binding Path="Name" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
If Type is not null then SomeString binding uses string format, and returns Type + space at the end. If Type is null, then binding return empty string.
SomeString is a reusable attached dependency property without special meaning. It declared as
public class Attached
{
public static readonly DependencyProperty SomeStringProperty =
DependencyProperty.RegisterAttached("SomeString", typeof (string), typeof (Attached));
public static void SetSomeString(UIElement element, string value)
{
element.SetValue(SomeStringProperty, value);
}
public static string GetSomeString(UIElement element)
{
return (string)element.GetValue(SomeStringProperty);
}
}
TextBlock has Tag property which can be used to store (or bind) some data, but Tag is of type object, and doesn't use StringFormat from binding. So I couldn't use Tag for my purpose here, and had to create another property, which has string type. (note also special syntax to bind attached property)

Related

Whis is the column not resize?

I have a datagrid and I want to set the width of a column according to some values, so I am trying to use a multibinding in this way:
<DataGridTextColumn.Width>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.MyProperty" />
<Binding Source="0"/>
</MultiBinding>
</DataGridTextColumn.Width>
The converter is fired, but the width is not changed according to de value it returns.
However, I have a similiar converter to set the visibility and it works as expected:
<DataGridTextColumn.Visibility>
<MultiBinding Converter="{StaticResource MyConverterVisibilityMultiValueConverter}">
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.NombreProveedor" />
<Binding Source="0"/>
</MultiBinding>
</DataGridTextColumn.Visibility>
Why does it work with the visibivilty but not with the width?
Thanks.
EDIT:
I have tried setting the width of the textblock inside the column in this way:
Value converter:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//I have tried both ways, DataGridLength and return 20
//return new DataGridLength(20, DataGridLengthUnitType.SizeToHeader);
return 20;
}
Xaml: Option 1, setting the width directly, it works
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Width" Value="20"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
Xaml: option 2: trying with value converter, it doesn't work
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Width">
<Setter.Value>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}" >
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.Property1" />
<Binding Source="{x:Reference ProxyElement}" Path="DataContext.Property2" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
The converter is rised and it returns 20, but the column doesn't take this value.
Ensure that the converter returns a DataGridLength value:
return new DataGridLength(100, DataGridLengthUnitType.Pixel);

WPF How to set visibility of parent container from a data template

I have create a DataTemplate for my Component object. I added DataTrigger to determine if the component should be visible or not. Essentially we have filters and the code checks those filters to determine if the component should be Visible or Collapse. The issue I have is that I want the trigger to set the visibility to "Collapse" or "Visible" of the parent container, i.e a ListBoxItem. The code works but sets it at the Border instead.
The template starts like this:
<DataTemplate DataType="{x:Type local:Component}">
<Border .....
I am providing the code for my Trigger and I'll explain what I tried below without success.
<DataTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TrueWhenComponentIsVisible}">
<Binding Path="Type" />
<Binding Path="Dependency"/>
<Binding Path="SelectedType" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
<Binding Path="SelectedDepencency" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TrueWhenComponentIsVisible}">
<Binding Path="Type" />
<Binding Path="Dependency"/>
<Binding Path="SelectedType" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
<Binding Path="SelectedDepencency" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger.Setters>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
For the Setter Property
<Setter Property="Visibility" Value="Collapsed"></Setter>
I attempted to use the binding to get the listboxitem like this:
<Setter Property="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBoxItem}, Path=Visibility}" Value="Collapsed"></Setter>
I get this error when I try to run it, so I assume I can't use binding there at all and need a different approach?
A 'Binding' cannot be set on the 'Property' property of type 'Setter'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject
Worked for me.
Don't know why it did not work for you?
<ListBox x:Name="lb" ItemsSource="{Binding}" DisplayMemberPath="Text">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding Path=Vis}" />
</Style>
</ListBox.Resources>
</ListBox>
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
List<TextVis> TextViss = new List<TextVis>();
TextVis tv1 = new TextVis();
tv1.Text = "tv1";
tv1.Vis = System.Windows.Visibility.Hidden;
TextViss.Add(tv1);
TextVis tv2 = new TextVis();
tv2.Text = "tv2";
tv2.Vis = System.Windows.Visibility.Visible;
TextViss.Add(tv2);
lb.ItemsSource = TextViss;
}
public class TextVis
{
public string Text { get; set; }
public Visibility Vis { get; set; }
}
}
I want the trigger to set the visibility to "Collapse" or "Visible" of
the parent container, i.e a ListBoxItem
Change the ItemContainerStyle, like this:
<ListBox ...>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource TrueWhenComponentIsVisible}">
<Binding Path="Type" />
<Binding Path="Dependency"/>
<Binding Path="SelectedType" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
<Binding Path="SelectedDepencency" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
BTW, you should really create a proper ViewModel and move all this logic to the ViewModel level instead of so much MultiBinding and Converter-based stuff.

Bind a property to an existing Binding

I have an interface with multiple buttons. I'd like to enable or disable these buttons according to a 'complex' condition. I declared this MultiBinding as an application resource in order to avoid code repetition:
<MultiBinding x:Key="MyMultiBinding" Converter="{StaticResource ResourceKey=MyConverter}">
<Binding Path="IsConnected" />
<Binding Path="IsOpened" />
</MultiBinding>
Here is how I declare my button:
<Button Name="MyButton" Content="Click me!" IsEnabled="{StaticResource ResourceKey=MyMultiBinding}" />
At runtime, I get the following error: "Set property IsEnabled threw an exception... MultiBinding is not a valid value for property IsEnabled".
I can't figure why this is not working. Could you please point me to the right way to do this? Thank you.
You can't set the boolean IsEnabled property to a value of type MultiBinding. That is what is happening.
As #Viv pointed out, you could declare a Style to do the heavy lifting:
<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding Converter="{StaticResource ResourceKey=MyConverter}">
<Binding Path="IsConnected" />
<Binding Path="IsOpened" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
<Button Name="MyButton" Content="Click me!" Style="{StaticResource ButtonStyle}" />
This works well if the Button DataContext has those properties. It works especially well if they each have a different DataContext they are bound to, enabling them for different reasons.
If they are all bound to the same DataContext, or the properties are on a different object, you could use the Freezable Trick to provide a value that your buttons would bind to:
<BindingProxy x:Key="isEnabled">
<BindingProxy.Data>
<MultiBinding Converter="{StaticResource ResourceKey=MyConverter}">
<Binding Path="IsConnected" />
<Binding Path="IsOpened" />
</MultiBinding>
</BindingProxy.Data>
</BindingProxy>
<Button Name="MyButton" Content="Click me!" IsEnabled="{Binding Data, Source={StaticResource isEnabled}}" />
I don't know if this is the best solution, but wrapping the MultiBinding into a style, as Viv said, did the trick. Here is the code of the Style :
<Style x:Key="MyStyle" TargetType="Button">
<Style.Setters>
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding Converter="{StaticResource ResourceKey=MyConverter}">
<Binding Path="IsConnected" />
<Binding Path="IsDataAccessOpened" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
And the code of the button :
<Button Name="MyButton" Content="Click me!" Style={StaticResource ResourceKey=MyStyle} />

how to pass current Combobox item in converter in a style

I am trying to create a style for comboxitem. I want to pass the current comboboxitem to converter. Style is like
<Style x:Key="MyVisibilityStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource VisibiltyMultiValueConverter}">
<Binding Path="."/>
<Binding Path="SelectedItem"
ElementName="ABCComboBox"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
Problem is the "." is passing the object of MainWindow not the comboboxitem.
Through <Binding Path="."> youre passing the object which the ComboBoxItem holds, but with <Binding RelativeSource="{RelativeSource Self}"/> you can pass the control itself.
What you also could do is passing the whole ComboBox and its selected index/item:
and in your converter you could get your ComboBoxItem like so:
ComboBoxItem cbi = (ComboBoxItem)(cb.ItemContainerGenerator.ContainerFromIndex(selectedindex));
or
ComboBoxItem cbi = (ComboBoxItem)(cb.ItemContainerGenerator.ContainerFromItem(selecteditem));
You can get the selected item of the combobox by using FindAncestor:
<Binding Path="SelectedItem" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ComboBox}"/>

Current datagrid item and multibinding

I have a DataGrid, in which I want to change the background of a row according to the values of the ItemSource, so I need to pass the current item, but I don't know how.
I am doing that:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource myDataGridBackgroundMultiValueConverter}">
<MultiBinding.Bindings>
<Binding ElementName="ucPrincipal" Path="DataContext.MyProperty01FromDataContext"/>
<Binding ElementName="ucPrincipal" Path="DataContext.MyProperty02FromDataContext"/>
<Binding ElementName="ucPrincipal" Path="DataContext.MyProperty03FromDataContext"/>
<Binding ElementName="dgdMyGrid" Path="CurrentItem"/>
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
The DataGrid has as ItemsSource a collection of MyDataClass (that has many properties) in the ViewModel. I need to pass to the converter the MyDataClass with the information of the row.
CurrentItem does not work, because I always receive null.
All the other parameters are ok.
DataGrid has no CurrentItem property, only CollectionViews have, DataGrid has a SelectedItem. You should see a binding error because of this as well.
If by current item you mean the data item to which the styled row belongs that would be the DataContext of the current row, which can be targeted via <Binding />.
Well, finnally, I get the way to pass the dataContext of the row to the converter. I do the following:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Visibility" Value="{Binding ChangeTracker.State, Converter={StaticResource visibilidadFilaBorradaConverter}}"/>
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource ucTareasMatenimientoDataGridBackgroundMultiValueConverter}">
<MultiBinding.Bindings>
<Binding ElementName="ucPrincipal" Path="DataContext.Property01"/>
<Binding Path="DataContext" RelativeSource="{RelativeSource Mode=Self}" />
<Binding ElementName="ucPrincipal" Path="DataContext.Property02"/>
<Binding ElementName="ucPrincipal" Path="DataContext.Property03"/>
<Binding ElementName="ucPrincipal" Path="DataContext.Property04"/>
</MultiBinding.Bindings>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
In the second parameter for the multivalue converter, I use the RelativeSource, to be able to pass the datContext of the row. In this way, I can compare its information with other properties of the principal dataContext of the control.

Resources