WPF ComboBox a better way to format ItemsSource - wpf

Morning Guys,
I have a few ComboBoxes bound to List of TimeSpan. I am formatting the TimeSpans using IValueConverter and ItemTemplate. I was wondering if there were an easier way to format the TimeSpans. Here's what I'm currently doing.
public class TimeSpanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return string.Empty;
TimeSpan t = TimeSpan.MinValue;
TimeSpan.TryParse(value.ToString(), out t);
return "{0:00}:{1:00}".F(t.Hours,t.Minutes);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return TimeSpan.Parse(value.ToString());
}
#endregion
}
<Canvas>
<Canvas.Resources>
<bc:TimeSpanConverter x:Key="ts" />
<DataTemplate x:Key="TimeSpanTemplate">
<TextBlock Text="{Binding ., Converter={StaticResource ts}}" />
</DataTemplate>
</Canvas.Resources>
<TextBlock Canvas.Left="6"
Canvas.Top="6"
Height="21"
Name="textBlock4"
Text="Begin"
Width="40" />
<TextBlock Canvas.Left="81"
Canvas.Top="6"
Height="21"
Name="textBlock5"
Text="End"
Width="40" />
<ComboBox Canvas.Left="7"
Canvas.Top="25"
Height="23"
Name="TimeBeginCombo"
ItemTemplate="{StaticResource TimeSpanTemplate}"
SelectedItem="{Binding TimeBegin}"
Width="68" />
<ComboBox Canvas.Left="81"
Canvas.Top="25"
Height="23"
Name="TimeEndCombo"
ItemTemplate="{StaticResource TimeSpanTemplate}"
SelectedItem="{Binding TimeEnd}"
Width="68" />
</Canvas>
</GroupBox>

Is what you're binding to of type TimeSpan? If so, then the converter can be much simpler
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is TimeSpan))
return string.Empty;
TimeSpan t = (TimeSpan)value;
return "{0:00}:{1:00}".F(t.Hours,t.Minutes);
}
And also, a general remark - are you sure you need to layout your UI on a Canvas and using absolute coordinates? :)

Related

WPF MarkupExtension binding error

refers to implementing a multilanguage interface now I have a problem. I have a grid where its ItemSource it's a Dictionary<string, string>: I can't give the key value as parameter of the markup extension because I have the following exception
A 'Binding' cannot be set on the 'Value1' property of type 'TranslateMarkupExtension'.
A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
This is the xaml where I use the markup extension:
<ItemsControl ItemsSource="{Binding MyDictionary}" Grid.Row="0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Content="{TranslateMarkup Value1 = {Binding Path = Key}}" Grid.Column="0" />
<TextBox Tag="{Binding Key, Mode=OneWay}" Text="{Binding Value, Mode=OneWay}" Width="200" Height="46" VerticalContentAlignment="Center" Grid.Column="1" LostFocus="TextBox_OnLostFocus" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I can't find any workaround..I need the dictionary as item source because the label will show the key and the textbox the corresponding value..I tried also replacing the dictionary with a list of object but the problem still remains.
Does anyone have a good hint? Thanks in advance.
As suggested by Ed Plunkett, the solution is the ValueConverter.
So the label is know this:
<Label Content="{Binding Key, Converter={StaticResource MultiLangConverter}}" Grid.Column="0" />
where my ValueConverter is the following:
public class StringToMultiLangConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var stringValue = (string) value;
if (string.IsNullOrEmpty(stringValue))
return string.Empty;
string multiLang = LangTranslator.GetString(stringValue);
return multiLang ?? value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
Thanks for the help!

WPF textbox expression bindings

I have two textboxes bound to a slider. I want the second textbox to be 1.2 time the value of the slide.
<Slider Maximum="500" Minimum="25" TickFrequency="5" IsSnapToTickEnabled="True" Name="slCellHeight" />
<TextBox Text="{Binding ElementName=slCellHeight, Path=Value, UpdateSourceTrigger=PropertyChanged}" Width="40" Name="txtCellHeight" />
<TextBox Text="{Binding ElementName=slCellHeight, Path=Value, UpdateSourceTrigger=PropertyChanged}" Width="40" Name="txtCellWidth" />
That is, when the slider shows 100, the first textbox(txtCellHeight) should show 100. This is working fine. I want to the second one to be 120.
I tried the calBinding but no success. Please suggest some good ways to do that.
Use a converter.
XAML:
<Window.Resources>
<local:MultiplyConverter x:Key="MultiplyConverter" />
</Window.Resources>
...
<TextBox Text="{Binding ElementName=slCellHeight, Path=Value,
UpdateSourceTrigger=PropertyChanged, Converter={StaticResource
MultiplyConverter}, ConverterParameter=1.2 }" Width="40" Name="txtCellWidth" />
Class MultiplyConverter:
class MultiplyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double result;
double.TryParse(parameter.ToString(), out result);
double mult = (double)value * result;
return mult;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I've done it on the fly, so maybe it will need some fixes to compile.

DataGrid Items collection doesn't always refresh

I have a DataGrid bound to an ObservableCollection<Client>.
I have a UserControl which is responsible to apply a filter on the collection from a Textbox value like this:
private void UCFilterBox_SearchTextChanged(object sender, string e)
{
var coll = CollectionViewSource.GetDefaultView(dgClients.ItemsSource);
coll.Filter = o =>
{
var c = o as Client;
if (c != null)
{
bool ret = (the filter...)
return ret;
}
else
{
return false;
}
};
}
Then I have a TextBlock which is bound to the DataGrid's Items collection like this:
<StackPanel Grid.Row="0"
Margin="215,0,0,5"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Orientation="Horizontal">
<TextBlock Style="{StaticResource SmallTextBlockStyle}" Text="{Binding ElementName=dgClients, Path=Items.Count}" />
<TextBlock Style="{StaticResource SmallTextBlockStyle}"
Text="{Binding ElementName=dgClients, Path=Items.Count, Converter={StaticResource ClientSingleOrPluralConverter}, StringFormat={} {0}}" />
</StackPanel>
This is working correctly, and each time the DataGrid is filtered, the value changes accordingly.
However, I have another TextBlock bound to the DataGrid's Items collection, which is responsible of showing the sum of the displayed data, and this one is not updating !
<TextBlock Margin="5"
FontWeight="Bold"
Text="{Binding ElementName=dgClients,
Path=Items,
Converter={StaticResource CalculateSumConvertor},
StringFormat={}{0:C}}" />
The CalculateSumConvertor is only hit once at the binding of the DataGrid and then no more.
Here is the converter:
public class CalculateSumConvertor: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var clients = value as ItemCollection;
if (clients != null)
{
return clients.Cast<Client>().Sum(c => c.FieldToSum);
}
else
{
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Is there something I'm doing wrongly here ?
Change your binding to the following and change your converter to an IMultiValueConverter
<TextBlock Margin="5"
FontWeight="Bold">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource CalculateSumConvertor}" StringFormat="{}{0:C}">
<Binding ElementName="dgClients" Path="Items" />
<Binding ElementName="dgClients" Path="Items.Count" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Converter:
class CalculateSumConvertor : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var clients = values[0] as ItemCollection;
...
}
}

How to reference another control in a TreeViewItem through a binding converter?

I would like to bind the Visibility of a TextBox based on SelectedItem of a ComboBoxin same TreeViewItemContainer. I think I can use a Converter for the Binding but I don't know how to send the ComboBox item as a parameter of the TextBox Binding. Can this be done?
<TreeView>
<TreeView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox Margin="2,0" Name="SkillSelectCB" ItemsSource="{Binding PotentialChildren}" />
<TextBox Margin="2,0" Width="50" Visibility="{Binding ??}" />
<Button Margin="2,0" Content="Add" />
</StackPanel>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
This is actually in a HierarchicalDataTemplate, the example above is very minimal. The "Add" Button will add new children to the ViewModel for the TreeView based on what's selected in the ComboBox. And the visibility is the TextBox will change depending on some property of the ComboBox's SelectedItem.
So the Xaml for the TextBox:
<TextBox Margin="2,0"Width="50" Visibility="{Binding SelectedItem, ElementName=SkillSelectCB, Converter={StaticResource SkillToVisibilityConverter}}" />
And the Converter:
public class SkillToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = (Skill)value;
return (s == null || !s.Specialized) ? "Hidden" : "Visible";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Binding a Distinction basedon Actual Width to Visibility

Is it possible to bind an ActualWidth or Width property of a Control to the Visibility of another Control with a distinction about value (like <200)? In my Opinion it is only possible with a converter because a DataTrigger can not work with > or <.
So I tried it with a Converter but it didn't work. I'm not sure which BindingMode is necessary and which kind of converter I need for such a solution.
The xaml code:
<StackPanel>
<Slider x:Name="slider" Height="36" Width="220" Maximum="500"/>
<Rectangle x:Name="mover" Height="12" Stroke="Black" Width="{Binding Value, ElementName=slider}"/>
<Rectangle x:Name="rectangle" Fill="#FFFF9E0E" Height="34" Width="112" Visibility="{Binding ActualWidth, Converter={StaticResource umkehr}, ElementName=rectangle, Mode=OneWay}"/>
</StackPanel>
and the idea for the converter:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null) {
var val = System.Convert.ToDouble(value);
if (val > 100)
return Visibility.Visible;
return Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
It is likely not working because you are binding your Rectangle's Visibility to the same rectangle's ActualWidth, and an invisible rectangle will always have a width of 0, so will never be visible.
Here's some examples. One binds to the other rectangle's ActualWidth, and the other binds to your Slider's Value
<Rectangle x:Name="rectangle"
Visibility="{Binding ActualWidth, ElementName=mover,
Converter={StaticResource umkehr}}"/>
or
<Rectangle x:Name="rectangle"
Visibility="{Binding Value, ElementName=slider,
Converter={StaticResource umkehr}}"/>
And as far as I know, there's no easy way of basing a value off of if something is greater than or less than a value. Coverters are your best option.
ActualWidth is a readonly property exposed by FrameworkElement class -
public double ActualWidth { get; }
It is get only property hence you can't set it to other value from code. You can bind to Width of your control instead to make it work.
EDIT
This works for me, may be this is what you want -
Converter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double)
{
return ((double)value > 100) ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
XAML
<StackPanel>
<Slider x:Name="slider" Height="36" Width="220" Maximum="500"/>
<Rectangle x:Name="mover" Height="12" Stroke="Black" Width="{Binding Value, ElementName=slider}"/>
<Rectangle x:Name="rectangle" Fill="#FFFF9E0E" Height="34" Width="112" Visibility="{Binding ActualWidth, Converter={StaticResource MyConverter}, ElementName=mover, Mode=OneWay}"/>
</StackPanel>
If you're attempting to change the Visibility of a control based on the ActualWidth of another control, you'll either need to use a IValueConverter or you're own type of MarkupExtension (inherit from Binding or BindingBase).
Converter Option:
[ValueConversion(typeof(Double), typeof(Visibility))]
[ValueConversion(typeof(Double?), typeof(Visibility))]
public class MinimumLengthToVisibilityConverter : IValueConverter
{
public Double MinLength { get; set; }
public override Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
if ((value == null) || !(value is Double))
{
return DependencyProperty.UnsetValue;
}
return (((Double)value) > MinLength) ? Visibility.Visible : Visibility.Collapsed;
}
public override Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
MarkupExtension Option:
Read this blog post to get a better feel for how to implement this...
You can actually have the value in a parameter, so you can re-use the converter if you need to:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double cutoff = 0.0;
if(parameter is double)
{
cutoff = (double)parameter;
}
if (parameter is string)
{
Double.TryParse(parameter.ToString(), out cutoff);
}
if (value is double)
{
return ((double)value > cutoff) ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
And the XAML:
<StackPanel>
<Slider x:Name="slider" Height="36" Width="220" Maximum="500"/>
<Rectangle x:Name="mover" Height="12" Stroke="Black" Width="{Binding Value, ElementName=slider}"/>
<Rectangle x:Name="rectangle" Fill="#FFFF9E0E" Height="34" Width="112"
Visibility="{Binding ActualWidth, Converter={StaticResource ActualWidthToVisibilityConverter},
ElementName=mover, Mode=OneWay, ConverterParameter=100}"/>
</StackPanel>

Resources