WPF binding to an observable collection with hash key as index - wpf

The following code:
<UserControl x:Class="MyProgram.Views.MyControlContainer"
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:local="clr-namespace:MyProgram.Views"
xmlns:common_model="clr-namespace:MyProgram.Models;assembly=CommonTitan"
mc:Ignorable="d"
>
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<local:MyControl x:Name="MyControl1" DataContext="{Binding MyControlCollection[8]}" Grid.Row="0" Grid.Column="0"/>
<local:MyControl x:Name="MyControl2" DataContext="{Binding MyControlCollection[9]}" Grid.Row="0" Grid.Column="1"/>
<local:MyControl x:Name="MyControl3" DataContext="{Binding MyControlCollection[10]}" Grid.Row="0" Grid.Column="2"/>
</Grid>
</UserControl>
Appropriately (as far as i can tell) binds me to the items that i specify in a collection. In real use it will use an observable dictionary and those indexes are hash values that i won't be able to hand code in. Is it possible to pass in the 2 known parameters into some sort of converter in order to get the hash? or even pass in the 2 values to the converter which will end up returning the item out of the observable dictionary?

You can pass 2 known value as converter parameters, for example:
<local:MyControl x:Name="MyControl1" DataContext="{Binding MyControlCollection, Converter={StaticResource CollectionConverter}, ConverterParameters='8.9'}" />
And in your converter class you can use it here, parse the parameter as string:
public class CollectionConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter != null)
{
string[] param = (parameter as string).split('.');
if (int.Parse(param[0]) == 8)
return (value as ObservableCollection)[8];
else
return (value as ObservableCollection)[0];
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException("cannot cast the value");
}
}

Related

Texttrimming in Auto grid column

I want something very simple in WPF, but I don't get it to work:
I have a grid with 2 columns: one * and one Auto. The second column contains a TextBlock. I need texttrimming to work with this TextBlock. This doesn't work currently, because the TextBlock goes outside the bounds of the grid.
Extra info:
The second column should be juste wide enough to contain the TextBlock. The first column should contain all remaining space. If the Grid isn't wide enough to contain the desired width of the TextBlock, the text should be trimmed.
The width of the Grid changes when resizing the window.
Nothing is static (not the text, no sizes), so hardcoded values can not be used.
ClipToBounds property doesn't fix this issue.
I can't bind MaxWidth of the TextBlock to the width of the column, otherwise the TextBlock will only getting smaller, but never bigger when resizing the window.
Code to reproduce the issue (for example in Kaxaml):
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<Grid Height="20" Background="Blue" DockPanel.Dock="Top" Margin="100 0 100 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" MaxWidth="200"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="1"
Background="Red"
Text="Test tralalalalalalalalalala long string this should be trimmed!"
TextTrimming="CharacterEllipsis"/>
</Grid>
</DockPanel>
</Page>
Any suggestions?
Second solution:
Use a Converter like this:
namespace StackStuff{
class WidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is Double)
{
return (double)value - 200; // 200 = 100+100 form the grid margin
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
In the View, you will have:
xmlns:local="clr-namespace:StackStuff"
Then, you have to add the converter for it to be used:
<Window.Resources>
<local:WidthConverter x:Key="WidthConverter"/>
</Window.Resources>
And then you have to implement the converter:
<DockPanel Background="Green" x:Name="dock">
<Grid Height="20" Background="Blue" DockPanel.Dock="Top" Margin="100 0 100 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock MaxWidth="{Binding ActualWidth, Converter={StaticResource WidthConverter}, ElementName=dock}"
Grid.Column="1"
Hope this is what you wanted.

How can I have only a * notation wpf grid column resize when the containing control is resized and not clip other columns?

I have a grid with certain column settings:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*" MinWidth="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="240" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="100*" />
</Grid.RowDefinitions>
....Omitting content to keep this simple....
</grid>
When I resize the width of the containing control, the center column resizes as expected, to a point. Then it will start to clip the third column for no apparent reason (there's still room for the center column to shrink). How can I force WPF to only resize the center column, and only clip the third column if the center column's width is at 0?
EDIT:
Try using a MultiValueConverter to minimize the MaxWidth of the second column to the available space in the grid. The code below should do the trick:
Xaml:
<Grid.ColumnDefinitions>
<ColumnDefinition Name="col1" Width=".25*" MinWidth="200"/>
<ColumnDefinition Name="col2" Width="*">
<ColumnDefinition.MaxWidth>
<MultiBinding Converter="{StaticResource GridWidthConverter}">
<Binding ElementName="col1" Path="MinWidth"/>
<Binding ElementName="col3" Path="Width"/>
<Binding ElementName="Control" Path="ActualWidth"/>
</MultiBinding>
</ColumnDefinition.MaxWidth>
</ColumnDefinition>
<ColumnDefinition Name="col3" Width="240" />
</Grid.ColumnDefinitions>
Converter:
public class GridWidthConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var col1 = System.Convert.ToDouble(values[0]);
var col2 = System.Convert.ToDouble(((GridLength)values[1]).Value);
var control = System.Convert.ToDouble(values[2]);
var maxWidth = control - (col1 + col2);
return maxWidth > 0 ? maxWidth : 0;
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
#endregion
}
You may want to add some error checking to the converter but it should give you the idea.
I ended up figuring out the problem. Richard E led me in the right direction. Apparently the act of setting a * notation on a column and a minimum width caused the behavior I was experiencing. Specifically, when column 1 stopped shrinking due to hitting the minimum width, column 2 only continued to shrink at a rate that it would have if column 1 continued to shrink as well. This version of the xaml works correctly:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="240" />
</Grid.ColumnDefinitions>
I don't know if the behavior I encountered was intentional or not.
Thanks for the help!

Silverlight BusyIndicator

Is there a way to not show the BusyIndicator when it is not busy (IsBusy='false')? After I added the silverlight BusyIndicator into my UserControl it uses a large area so all other controls are moved down and the GUI looks not good anymore. I need it is hided when it's no busy and shows up when it's busy.
Thanks for help.
CK
I would use a standard BooleanToVisiblityConverter and bind the Visibilty to the IsBusy property like such:
<Grid Height="500" Width="500" Background="Blue">
<Grid.Resources>
<Converters:BoolToVisConverter x:Key="BoolToVis"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Width="75">
<ToggleButton x:Name="BusyButton" Content="Toggle Busy State"/>
<ToggleButton x:Name="ProgressButton" Content="Toggle ProgressButton State"/>
</StackPanel>
<toolkit:BusyIndicator Grid.Row="1" IsBusy="{Binding IsChecked, ElementName=BusyButton}"
Visibility="{Binding IsBusy, RelativeSource={RelativeSource Self}}"/>
<ProgressBar Grid.Row="2" Width="120" Height="10" Margin="4 2" VerticalAlignment="Center" IsIndeterminate="True"
Visibility="{Binding IsChecked, ElementName=ProgressButton, Converter={StaticResource BoolToVis}}"/>
</Grid>
I've provided both a BusyIndicator as well as ProgressBar is this example so you can see both in action.
The BooleanToVisibilityConverter is pretty standard and is implemented as such:
public class BoolToVisConverter : IValueConverter
{
#region IValueConverter Members
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return Visibility.Collapsed;
return (bool)value == true ? Visibility.Visible : Visibility.Collapsed;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}

Can we manipulate (subtract) the value of a property while template bidning?

I am currently defining few grids as following:
<Grid.RowDefinitions>
<RowDefinition Height="{TemplateBinding Height-Height/5}"/>
<RowDefinition Height="{TemplateBinding Height/15}"/>
<RowDefinition Height="{TemplateBinding Height/20}"/>
<RowDefinition Height="{TemplateBinding Height/6}"/>
</Grid.RowDefinitions>
While the division works fine , the subtraction isn't yielding the output.
Ialso tried like following:
<RowDefinition Height="{TemplateBinding Height-(Height/5)}"/>
Still no result. Any suggestions plz.
Thanks,
Subhen
**
Update
**
Now In My XAML I tried implementing the IvalueConverter like :
<RowDefinition Height="{TemplateBinding Height, Converter={StaticResource heightConverter}}"/>
Added the reference as
<local:medieElementHeight x:Key="heightConverter"/>
In side generic.cs I have coded as following:
public class medieElementHeight : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//customVideoControl objVdeoCntrl=new customVideoControl();
double custoMediaElementHeight = (double)value;//objVdeoCntrl.customMediaPlayer.Height;
double mediaElementHeight = custoMediaElementHeight - (custoMediaElementHeight / 5);
return mediaElementHeight;
}
#region IValueConverter Members
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
But getting the exception Unknown Element Height in the element RowDefination.
Updating Code #Tony
<Style TargetType="local:customVideoControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:customVideoControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{TemplateBinding Height-Height/5}"/>
<RowDefinition Height="{TemplateBinding Height/15}"/>
<RowDefinition Height="{TemplateBinding Height/20}"/>
<RowDefinition Height="{TemplateBinding Height/6}"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<MediaElement x:Name="customMediaPlayer"
Source="{TemplateBinding CustomMediaSource}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
Grid.Row="0" Grid.ColumnSpan="3"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now , My implementing XAML file contains is as follows:
<customMediaElement:customVideoControl x:Name="custMediaElement" Width="400" Height="300" nextBtnEvent="custMediaElement_nextBtnEvent" prevBtnEvent="custMediaElement_prevBtnEvent" Visibility="Collapsed"/>
Now, I want to substract or divide the values depending on the height of custMediaElement.
You have posed several good questions; however, we often get caught up in finding a solution while overlooking the original problem. Before attempting to answer your questions I would like to explorer you layout expectations.
Your provided code suggests that you have a custom control (customVideoControl) whose instance is a height of 300px. The ControlTemplate applied to this control has 4 rows, which have calculated heights based on the instance height. Based on these settings your 4 rows would have the following values:
Row 0: 240
Row 1: 20
Row 2: 60
Row 3: 50
These total to 370px, which is 70px larger than the control. This means that Row 3 would be completely hidden from view, and Row 2 would be clipped down to the top 40px. I assume that this is not your intention. If this is your intention, then the answers below will hopefully help you on your path. If your intention is to scale the row heights based on a ratio, then you can use star sizing. Your suggested ratio would use the following settings:
<Grid.RowDefinitions>
<RowDefinition Height="240*"/>
<RowDefinition Height="20*"/>
<RowDefinition Height="60*"/>
<RowDefinition Height="50*"/>
</Grid.RowDefinitions>
If you would still like to measure the height of the rows then there are a few corrections that you need to make.
Mathematical operations cannot be performed within markup extensions (the curly braces). Your division approach may not be throwing a xaml parse exception, but I doubt that it is working correctly. A value converter is necessary to accomplish what you want.
TemplateBinding is really just a light-weight version of a RelativeSource binding. Since TemplateBinding is light-weight it doesn't allow for Converters.
To get your expected behavior you need to use a Binding with a RelativeSource. Therefore the code you want looks something like this:
<RowDefinition Height="{Binding Path=Height,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource DivisionConverter},
ConverterParameter=15}"
/>
Where DivisionConverter is the key of a custom converter. The ConverterParameter in this example allows the developer to pass in a denominator, instead of having to create a separate converter for each number.
Here's an example of the custom DivisionConverter you will need to create:
public class DivisionConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Default to 0. You may want to handle divide by zero
// and other issues differently than this.
double result = 0;
// Not the best code ever, but you get the idea.
if (value != null && parameter != null)
{
try
{
double numerator = (double)value;
double denominator = double.Parse(parameter.ToString());
if (denominator != 0)
{
result = numerator / denominator;
}
else
{
// TODO: Handle divide by zero senario.
}
}
catch (Exception e)
{
// TODO: Handle casting exceptions.
}
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
If you wanted to incorporate both division and subtraction in the same Binding you would need to either create a special converter, or use a MultiBinding (which would also require you to create a special MultiBindingConverter).
I think you have to use Converter for this purpose
Instead of subtracting one-fifth, why don't you just multiply by .8?

All ComboBoxes in a ListBox change when any 1 of them is changed

I have a ListBox on a form that is bound to an ObservableCollection of a custom type. Within each item there is a ComboBox bound to an enumeration type. When the window loads, all ComboBoxes are defaulted to a certain value. When I change the SelectedItem for any one (from the UI, not from code), all other ComboBoxes change to the same SelectedItem.
I'm not sure what I've done wrong, here is my current XAML that is handling this.
<Window.Resources>
<ObjectDataProvider x:Key="SyncOperationValues"
MethodName="GetNames"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:SyncOperationEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
...
<DataTemplate x:Key="SyncListTemplate">
<Grid Grid.Column="1" Grid.RowSpan="2" Margin="0,0,20,0" x:Name="olDetails"
DataContext="{Binding Path=OlContact}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
...
<ComboBox x:Name="SyncOp"
Width="120" Height="19"
Margin="4,0,10,0"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource SyncOperationValues}}"
SelectedItem="{Binding Operation}"
VerticalAlignment="Center" />
...
and the ListBox:
<ListBox x:Name="SyncList"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ItemContainerStyle="{StaticResource StretchedContainerStyle}"
ItemTemplate="{StaticResource SyncListTemplate}">
ListBox>
I have tried some different options, like binding to a CollectionView; however nothing seems to work. Can anyone point me to my mistake?
Thanks!
I had a situation similar to this and setting the IsSynchronizedWithCurrentItem property on the ComboBox to "False" fixed it. The way I understand it, setting the value to "True" means the ComboBox value will be synchronized value of the current item for the ListBox. Basically, all the ComboBoxes are bound to that same value. It sounds like that is what you are experiencing. Try:
IsSynchronizedWithCurrentItem="False"
I have finally found a solution. I ended up writing a ValueConverter for the enumeration type. I'd been under the impression that this was not necessary, but for some reason it is, at least if the ComboBox is within another list (ListBox in my case) of some sort.
I did need to set the IsSynchronizedWithCurrentItem property to false as John M suggested, so thanks to John for that! Here is the converter code in case anyone else needs to do something like this.
[ValueConversion( typeof( SyncOperationEnum ), typeof( String ) )]
public class SyncOperationConverter : IValueConverter {
#region IValueConverter Members
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
if( value != null && value.GetType() == typeof( SyncOperationEnum ) )
return Enum.GetName( typeof( SyncOperationEnum ), value );
return null;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
if( value != null && targetType == typeof( SyncOperationEnum ) )
foreach( object enumValue in Enum.GetValues( targetType ) )
if( value.Equals( Enum.GetName( targetType, enumValue ) ) )
return enumValue;
return null;
}
#endregion
And my XAML now looks like this:
<Window.Resources>
<local:SyncOperationConverter x:Key="SyncConverter" />
<ObjectDataProvider x:Key="SyncOperationValues"
MethodName="GetNames"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:SyncOperationEnum" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
...
<DataTemplate x:Key="SyncListTemplate">
<Grid Grid.Column="1" Grid.RowSpan="2" Margin="0,0,20,0" x:Name="olDetails"
DataContext="{Binding Path=OlContact}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
...
<ComboBox x:Name="SyncOp"
Width="120" Height="19"
Margin="4,0,10,0"
IsSynchronizedWithCurrentItem="False"
ItemsSource="{Binding Source={StaticResource SyncOperationValues}}"
SelectedValue="{Binding Path=Operation,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,Converter={StaticResource SyncConverter}}"
VerticalAlignment="Center" />
It looks like your "Operation" property should be a Static property. Since it bind to every ComboBox when you change it,
So everything else is right in your XAML just make the property like bellow
static SyncOperationEnum _operation;
public static SyncOperationEnum Operation
{
get { return _operation; }
set { _operation = value;}
}

Resources