I have a ResourceDictionary made up of icons/Canvas objects drawn with Paths. My ViewModel includes a string property (IconName) that contains a string matching one of the entries in the ResourceDictionary. I developed a MultiBinding (IMultiValueConverter) that takes the string, and a FrameworkElement and does a resource lookup, returning the resource matching the name. Before getting to this point, I stubbed my View explicitly with the following:
<Rectangle Width="10" Height="10" Margin="0,0,10,0">
<Rectangle.Fill>
<VisualBrush Stretch="Fill" Visual="{StaticResource defalt_icon}" />
</Rectangle.Fill>
</Rectangle>
This renders correctly. However, when I switch out to the following, nothing is rendered in the Rectangle.
<Rectangle Width="10" Height="10" Margin="0,0,10,0">
<Rectangle.Fill>
<VisualBrush Stretch="Fill">
<VisualBrush.Visual>
<MultiBinding Converter="{StaticResource IconNameConverter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource AncestorType=FrameworkElement}"/>
<Binding Path="IconName"/>
</MultiBinding.Bindings>
</MultiBinding>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
My converter (show below) is being called, and does find the Canvas object and returns it (viewing the object in the debugger I can see that Canvas has a Path child that has the right Data member filled in).
public class IconNameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
string iconName = values[1] as string;
if (iconName == null)
return null;
FrameworkElement newIcon = (FrameworkElement)targetElement.TryFindResource(iconName);
if (newIcon == null)
newIcon = (FrameworkElement)targetElement.TryFindResource("appbar_page_question");
return newIcon;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Any ideas why the canvas isn't showing up?
Related
Is there a way that I can use a normal Rectangle (shape) as part of a clip for another object in XAML. It seems like I should be able to, but the solution is eluding me..
<Canvas>
<Rectangle Name="ClipRect" RadiusY="10" RadiusX="10" Stroke="Black" StrokeThickness="0" Width="32.4" Height="164"/>
<!-- This is the part that I cant quite figure out.... -->
<Rectangle Width="100" Height="100" Clip={Binding ElementName=ClipRect, Path="??"/>
</Canvas>
I know that I can use a 'RectangleGeometry' type approach, but I am more interested in the solution in terms of the code presented above.
Try Shape.RenderedGeometry Property.
<Rectangle Width="100" Height="100"
Clip="{Binding ElementName=ClipRect, Path=RenderedGeometry}" />
ClipRect.DefiningGeometry nd ClipRect.RenderedGeometry contain only the RadiusX and RadiusY values but not also Rect.
I'm not sure what exactly you want to achieve (it's not clear to me from your sample) but you could write an IValueConverter which would extract the info you require from the referenced Rectangle:
public class RectangleToGeometryConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var rect = value as Rectangle;
if (rect == null || targetType != typeof(Geometry))
{
return null;
}
return new RectangleGeometry(new Rect(new Size(rect.Width, rect.Height)))
{
RadiusX = rect.RadiusX,
RadiusY = rect.RadiusY
};
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You would then use this converter in your binding definition:
<Rectangle Width="100" Height="100"
Clip="{Binding ElementName=ClipRect, Converter={StaticResource RectangleToGeometryConverter}}">
Of course you need to add the converter to your resources first:
<Window.Resources>
<local:RectangleToGeometryConverter x:Key="RectangleToGeometryConverter" />
</Window.Resources>
I have a WPF App that has (so far) 2 modes of display, regularmode and widgetmode.
I am using Prism 6 with MVVM design pattern.
MainWindowViewModel knows the mode of display.
ToolBarView has, as expected, a toolbar of buttons and the buttons shall be dynamically changed to different images depending on the mode of the view. If the mode is WidgetMode, it switches to the image with an identical name but with an '_w' added. So instead of "image.png", it's "image_w.png".
What I'd like to do is create a string in ToolBarView that is updated to either String.Empty or to "_w", depending on the mode. I'd also like the image root folder to be a global string, rather than a hardcoded string, so I have defined that in app.xaml.
<Application.Resources>
<sys:String x:Key="ImageURIRoot">/MyApp;component/media/images/</sys:String>
</Application.Resources>
Then in my toolbarview (a usercontrol), I did this:
<UserControl.Resources>
<converters:StringToSourceConverter x:Key="strToSrcConvert"/>
<sys:String x:Key="BtnImgSuffix">_w</sys:String>
.
.
.
</UserControl.Resources>
Note that the string is hardcoded; eventually, I will change it dynamically based off the windowmode.
I then put the Buttons in a Listbox
<ListBoxItem Style="{StaticResource MainButton_Container}">
<Button Command="{Binding ButtonActionDelegateCommand}" Style="{StaticResource Main_Button}">
<Image Source="{Binding Source={StaticResource ImageURIRoot}, Converter={StaticResource strToSrcConvert}, ConverterParameter='{}{0}button.png'}" />
</Button>
</ListBoxItem>
Converter code:
public class StringToSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is string)
{
return string.Format(parameter.ToString(), value);
}
return null;
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
So that works. But what I want is to have the ConverterParameter equal "{}{0}button{1}.png", where {0} is the URI Root and {1} is the suffix. But I can't figure out how to do it. I know it's simple, but I can't put my finger on it!
Please help!
Figured it out and it was through multibinding. The way I did it was create a converter that inherits from IMultiValueConverter. Its "Convert" method looks like this:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ImageSourceConverter conv = new ImageSourceConverter();
int suffixPos = ((String)parameter).Length - 4;
var returnValue = ((String)parameter).Insert(suffixPos, values[1].ToString());
returnValue = Path.Combine(values[0].ToString(), returnValue);
ImageSource imgsrc = conv.ConvertFromString(returnValue) as ImageSource;
return imgsrc;
}
The xaml looks like this:
<Image Height="30" Width="40" diag:PresentationTraceSources.TraceLevel="High">
<Image.Source>
<MultiBinding Converter="{StaticResource stringsToSrcConvert}" ConverterParameter="buttonImg.png">
<Binding Source="{StaticResource ImageURIRoot}"/>
<Binding Source="{StaticResource BtnImgSuffix}"/>
</MultiBinding>
</Image.Source>
</Image>
Also, had to modify the URIRoot
<Application.Resources>
<sys:String x:Key="ImageURIRoot">pack://application:,,,/MyApp;component/media/images/</sys:String>
</Application.Resources>
Thanks, Clemens!
I have a textbox where the user enters in a number that is to be used as the angle to rotate an image. Is there a way to convert that number in xaml to negative? So if the user enters 50, I want the image to rotate -50 degrees instead of +50. I'd rather not do it in the code-behind. Code for it is below:
<TextBox x:Name="testing" />
<Image Source="aaaa.png" x:Name="thisimage" >
<Image.LayoutTransform>
<RotateTransform CenterX="0.5" CenterY="0.5" Angle="{Binding Path=Text , ElementName=testing, UpdateSourceTrigger=PropertyChanged}" />
</Image.LayoutTransform>
</Image>
This would be a pure XAML solution:
<Image.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
<RotateTransform CenterX="0.5" CenterY="0.5"
Angle="{Binding Path=Text, ElementName=testing,
UpdateSourceTrigger=PropertyChanged}" />
<ScaleTransform ScaleX="-1"/>
</TransformGroup>
</Image.LayoutTransform>
I would still recommend using a binding converter.
Try following code.
Firstly create implementing IValueConverter:
public class AngleConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var angle = (int)value;
if (angle != null)
{
return -angle;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
XAML:
<TextBox x:Name="testing" />
<Image Source="aaaa.png" x:Name="thisimage" >
<Image.LayoutTransform>
<RotateTransform CenterX="0.5" CenterY="0.5" Angle="{Binding Path=Text , ElementName=testing, Converter={StaticResource angleConverter} UpdateSourceTrigger=PropertyChanged}" />
</Image.LayoutTransform>
Add reference to your converter:
xmlns:cr="using:[project].Converters"
Don't forget add to resource:
<UserControl.Resources>
<cr:AngleConverter x:Key="angleConverter"/>
</UserControl.Resources>
I have chart with column series in my application. I'm using solution from this tutorial to add annotation on top of columns:
http://blogs.msdn.com/b/delay/archive/2009/07/27/simple-column-labels-you-can-create-at-home-re-templating-the-silverlight-wpf-data-visualization-columndatapoint-to-add-annotations.aspx
When the bar is really tall the top part of the annotation is only partial visible, or not visible.
I do not know range of my data, so I can not set Maximum value on the vertical Axis.
How to solve this problem?
Like Andre said, the problem is your Margin, so you could use Multibinding to calculate the Margin like so:
<ControlTemplate TargetType="charting:ColumnDataPoint">
<Grid>
<Rectangle Name="clmnRectangle"
Fill="{TemplateBinding Background}"
Stroke="Black"/>
<Grid Background="#aaffffff"
HorizontalAlignment="Center"
VerticalAlignment="Top">
<Grid.Margin>
<MultiBinding Converter="{StaticResource ResourceKey=HeightToMargin}">
<Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType={x:Type charting:ColumnDataPoint}}"></Binding>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource AncestorType={x:Type charting:ColumnSeries}}"></Binding>
</MultiBinding>
</Grid.Margin>
<TextBlock Name="tbValue"
Text="{TemplateBinding FormattedDependentValue}"
FontWeight="Bold"
Margin="2"/>
</Grid>
</Grid>
</ControlTemplate>
The Converter could look like:
public class HeightToMarginConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo ci)
{
double clmnHeight = System.Convert.ToDouble(values[0]);
double chrtHeight = System.Convert.ToDouble(values[1]);
if (chrtHeight - clmnHeight < 20)
{
return new Thickness(0, clmnHeight - chrtHeight + 5, 0, 0);
}
else
{
return new Thickness(0, -20, 0, 0);
}
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo ci)
{
return null;
}
}
I want to increase Progress-bar value based on two textbox's Text. I wrote this XAML but there is an error "Two-way binding requires path or xpath" when I do MultiBinding in ProgressBar.Value
<Window.Resources>
<local:Class1 x:Key="ConverterM"/>
</Window.Resources>
<TextBox Height="23" HorizontalAlignment="Left" Margin="157,59,0,0"
Name="textBox1" VerticalAlignment="Top" Width="120" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="157,108,0,0"
Name="textBox2" VerticalAlignment="Top" Width="120" />
<ProgressBar Height="24" HorizontalAlignment="Left" Margin="120,160,0,0"
Name="progressBar1" VerticalAlignment="Top" Width="243" >
<ProgressBar.Value>
<MultiBinding Converter="{StaticResource ConverterM}">
<Binding />
<Binding ElementName="textBox1" Path="Text" />
<Binding ElementName="textBox2" Path="Text" />
</MultiBinding>
</ProgressBar.Value>
</ProgressBar>
Value Converter:
public class Class1 : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
if (values[1] != null && values[2]!=null)
{
if (((string)values[1]).Length==((string)values[2]).Length)
{
return 5.0;
}
}
else
{
return 0.0;
}
}
public object[] ConvertBack(object value,
Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I think that <Binding /> is not necessary. try to delete it and change indexes in converter.
Two-way binding requires path or xpath
This happens when you haven’t set the Path= on binding. By default WPF binding will take the Path= part by default.
To avoid this you need to give Path for each Binding you specify in MultiBinding. here in your case there was an empty binding which has no Path defined thats why you have experience with the above error.
I have came across the same issue but the accepted answer does not say what the error is, So thought of sharing this.