I have a window with a grid acting like a form. The window isn't my own and there is a new requirement to not show (ie, collapse) rows 4 and 5 based on a user selected context.
The two things I can think of to make this work is to either:
Have a converter on the row content which takes a bool and collapses the visibility if true.
Have a converter on the grid row height property.
I prefer the latter, but am at a loss to get the input value for the converter. The converter code and binding is below.
Can someone tell me what the binding should look like to make this work? Is there some easier way to do this?
Converter Code
[ValueConversion(typeof(GridLength), typeof(Visibility))]
public class GridLengthToCollapseVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || parameter == null) return Binding.DoNothing;
var result = (GridLength) value;
bool shouldCollapse;
Boolean.TryParse(parameter.ToString(), out shouldCollapse);
return shouldCollapse ? new GridLength() : result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
}
The Binding (this is where I am stuck)
Say I want the value of the height to be 30 unless the bound ShowLastName property is true.
The binding below isn't right, but what is?
<RowDefinition Height="{Binding Source=30, Converter={StaticResource GridLengthToCollapseVisibilityConv},ConverterParameter=ShowLastName}" />
Working Solution
[ValueConversion(typeof(bool), typeof(GridLength))]
public class GridLengthToCollapseVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || parameter == null) return Binding.DoNothing;
bool shouldCollapse;
Boolean.TryParse(value.ToString(), out shouldCollapse);
return shouldCollapse
? new GridLength(0)
: (GridLength) parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
}
<Grid.Resources>
<cvt:GridLengthToCollapseVisibilityConverter x:Key="GridLengthToCollapseVisibilityConv" />
<GridLength x:Key="AutoSize">Auto</GridLength>
<GridLength x:Key="ErrorLineSize">30</GridLength>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="{StaticResource AutoSize}" />
<RowDefinition Height="{StaticResource ErrorLineSize}" />
<RowDefinition Height="{Binding Path=HideLastName,
Converter={StaticResource GridLengthToCollapseVisibilityConv},ConverterParameter={StaticResource AutoSize}}" />
<RowDefinition Height="{Binding Path=HideLastName,
Converter={StaticResource GridLengthToCollapseVisibilityConv},ConverterParameter={StaticResource ErrorLineSize}}" />
</Grid.RowDefinitions>
You can't databind the ConverterParamater: http://social.msdn.microsoft.com/Forums/en/wpf/thread/88a22766-5e6f-4a16-98a6-1ab39877dd09
Why not switch the value and parameter if the height always is the same:
<RowDefinition Height="{Binding Source=ShowLastName, Converter={StaticResource GridLengthToCollapseVisibilityConv},ConverterParameter=30}" />
If you need to databind both values you could use multi-value bindings: http://msdn.microsoft.com/en-us/library/system.windows.data.imultivalueconverter.aspx
All you have to do is to swap Binding and Parameter.
If you still want both values to be databound, use MultiBinding, even if your second value is a constant. It's a hack, but it's the easiest way to pass extra value(s) into your converter.
Related
I have multiple canvas images of different types (image source, geometry, path) and wish to only show 1 depending on a string binding.
whats the best way to do this?
i'd like it to be reusable so i can place this code inside a user control and then have many of these images around the app and i select which 1 is shown.
Like so:
<CanvasImage Image="Pie"/>
<CanvasImage Image="Dog"/>
Would it be too computationally expensive to have them all declared in the user control view and use visibility bindings
Pie canvas example:
<canvas>
<Data ="m24,98,07">
</canvas>
Dog canvas example:
<canvas>
<image source="">
<canvas>
This converter return an image source directly, depending on the value it receives.
namespace TestTreeView.Views
{
public class StringToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string file = "";
string v = value as string;
switch (v)
{
case "Pie":
file = #".\path\to\your\pie.jpg";
break;
case "Dog":
file = #".\path\to\your\dog.jpg";
break;
default:
return null;
}
return new BitmapImage(new Uri(file, UriKind.RelativeOrAbsolute));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Usage in XAML:
<Window xmlns:local="clr-namespace:YourNamespace.Views" ...>
<Window.Resources>
<local:StringToImageConverter x:Key="stringToImageConverter"/>
</Window.Resources>
<Grid>
<Canvas>
<Image Source="{Binding YourString, Converter={StaticResource stringToImageConverter}}"/>
</Canvas>
</Grid>
</Window>
Original answer
I think you need to use a Converter.
It will take a ConverterParameter, a String, that will tell what the binded value is expected to be, and return a Visiblity to indicate if the canvas should be visible or not.
namespace YourNamespace.Views
{
public class StringToCanvasVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string v = value as string;
string p = parameter as string;
return (v != null && p != null && v == p) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Usage in XAML:
<Window xmlns:local="clr-namespace:YourNamespace.Views" ...>
<Window.Resources>
<local:StringToVisibilityConverter x:Key="stringToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Canvas Visibility="{Binding YourString, Converter={StaticResource stringToVisibilityConverter}, ConverterParameter=Pie}"/>
<Canvas Visibility="{Binding YourString, Converter={StaticResource stringToVisibilityConverter}, ConverterParameter=Dog}"/>
</Grid>
</Window>
The default unit for the size of every user control is px, but it is possible to easily set the size to a different unit, for instance:
<Canvas Height="29.7cm" Width="21cm" />
But what should I do if I want to bind these properties? How do I retain the information about my desired unit?
You can create a custom converter which converts the string representation to double (using LengthConverter):
[ValueConversion(typeof(string), typeof(double))]
public class SizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double size = (double)new LengthConverter().ConvertFrom(value.ToString());
return size;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("SizeConverter is a oneway converter.")
}
}
After that you can refer that converter from your XAML:
<src:SizeConverter x:Key="sizeConverter"/>
<Canvas Height="{Binding Path=Height, Converter={StaticResource sizeConverter}}"
Width="{Binding Path=Width, Converter={StaticResource sizeConverter}}" />
(Here Height and Width are strings available in the DataContext of your Canvas.)
I have this kind of code below, how can I bind the visibility of the Border to the visibility of all the labels?
Of course the number of rows and labels is not fixed.
<Border BorderBrush=Black
BorderThickness="1,1,1,1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label DataContext="{Binding MyObject[1]}"
Content="{Binding MyText}"
Visibility="{Binding IsVisible}"/>
<Label DataContext="{Binding MyObject[2]}"
Content="{Binding MyText}"
Visibility="{Binding IsVisible}"/>
[...]
</Grid>
</Border>
It depends on how you are changing the amount of rows and labels.
I assume that MyObject is a List<MyObject>. In that case what you can do is simply bind the list to the Visibility property with a Converter that loops through the objects checking if they are all invisible.
XAML:
Namespace:
xmlns:converters="clr-namespace:MyConverters"
Window:
<Window.Resources>
<converters:ObjectBorderVisibilityConverter
x:Key="MyObjectBorderVisibilityConverter"/>
</Window.Resources>
<Border BorderBrush=Black
BorderThickness="{Binding MyObject, Converter={StaticResource MyObjectBorderVisibilityConverter}">
[...]
Converters Code:
namespace MyConverters
{
public class ObjectBorderVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility v = Visibility.Hidden;
List<MyObject> myObjects = value as List<MyObject>;
foreach(Object myobject in myObjects)
{
if (myobject.IsVisible)
v = Visibility.Visible;
}
return v;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException("ObjectBorderVisibilityConvertercan only be used OneWay.");
}
}
}
Otherwise you are going to have to explain how you got the amount of rows and labels to be dynamic and we can work from there.
Hope this helps
u_u
EDIT
Well according to your comment you have a list of strings which contain the name of the object you want to display in each ListViewItem. I'm not going to ask why you are doing it this way, I assume you have a reason. I just wanna say have you tried Key Value pairs?
What I would do here is pass the grid itself as a parameter in the converter, and loop through its children using a LogicalTreeHelper inside the converter.
Revised Border:
<Window.Resources>
<converters:ObjectBorderVisibilityConverter
x:Key="MyObjectBorderVisibilityConverter"/>
</Window.Resources>
<Border BorderBrush=Black
BorderThickness="{Binding MyObject, Converter={StaticResource MyObjectBorderVisibilityConverter}", ConverterParameter={Binding ElementName=myGrid, BindsDirectlyToSource=True>
<Grid x:Name="myGrid">
[...]
Revised Converter
namespace MyConverters
{
public class ObjectBorderVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility v = Visibility.Hidden;
Grid myGrid = parameter as Grid;
List<MyObject> myObjects = value as List<MyObject>;
foreach (var child in LogicalTreeHelper.GetChildren(myGrid))
{
if(child.GetType() == typeof(System.Windows.Controls.Label)
if (((Label)child).Visibility = Visibility.Visible)
v = Visibility.Visible;
}
return v;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException("ObjectBorderVisibilityConvertercan only be used OneWay.");
}
}
}
I coded this all by hand so there's prolly a bunch of errors, but I hope you get the point.
u_u
I'm trying to bind element's Height value to Checkbox.IsChecked property. Why that's not working?
<Window.Resources>
<local:BoolToHeightConverter x:Key="BoolToHeightConverter"/>
</Window.Resources>
<Button Name="JustBtn" Content="Hello World"/>
<CheckBox IsChecked="{Binding ElementName=JustButton, Path=Height, Converter=BoolToHeightConverter}" />
[ValueConversion(typeof(Nullable<bool>), typeof(double))]
public class BoolToHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return double.NaN;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
It doesn't even initalize the window. Says:
'IValueConverter' type does not have a public TypeConverter class
There are a couple of problems. First, it looks like you are trying to modify the Height property when the CheckBox is checked. If this is the case, you should implement your logic in the ConvertBack method of the converter, and specify a Mode on the Binding. Secondly, your Binding should use a StaticResource to reference your converter:
<CheckBox IsChecked="{Binding ElementName=JustButton, Path=Height, Converter={StaticResource BoolToHeightConverter}, Mode=OneWayToSource}" />
I'm sorry - my bad: I forgot to attach converter through StaticResource.
Sorry guys...
I have an List values with a ResourceKey and a Caption, these values are both strings. The Resource is the name of an actual resource defined in a resource dictionary. Each of these ResourceKey Icons are Canvas's.
<Data ResourceKey="IconCalendar" Caption="Calendar"/>
<Data ResourceKey="IconEmail" Caption="Email"/>
I then have a list view which has a datatemplate with a button and a text caption below the button. What I want to do is display Resource static resource as the content for the button.
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Content="{Binding ResourceKey}" Template="{StaticResource RoundButtonControlTemplate}"/>
<TextBlock Grid.Row="1" Margin="0,10,0,0" Text="{Binding Caption}" HorizontalAlignment="Center" FontSize="20" FontWeight="Bold" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
I think I have tried every permutation with binding staticresource etc.
I am open to alternatives, I know it may be easier to just have an image and set the source property.
Thanks
After having a little think I ending up using a ValueConvertor like so:
class StaticResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var resourceKey = (string)value;
return Application.Current.Resources[resourceKey];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}
and the binding on the button becomes
<Button Content="{Binding ResourceKey, Converter={StaticResource resourceConverter}}" />
Here I've got an improved version of #dvkwong 's answer (along with #Anatoliy Nikolaev 's edit):
class StaticResourceConverter : MarkupExtension, IValueConverter
{
private Control _target;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var resourceKey = (string)value;
return _target?.FindResource(resourceKey) ?? Application.Current.FindResource(resourceKey);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var rootObjectProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
if (rootObjectProvider == null)
return this;
_target = rootObjectProvider.RootObject as Control;
return this;
}
}
usage:
<Button Content="{Binding ResourceKey, Converter={design:StaticResourceConverter}}" />
The primary change here is:
The converter is now a System.Windows.Markup.MarkupExtension so it can be used directly without being declared as a resource.
The converter is context-aware, so it will not only look up in your App's resources, but also local resources (current window, usercontrol or page etc.).