Correctly Size Icon Bound to Canvas Object

2019-07-30 04:03发布

问题:

I have to following method that retrieves Canvas object from a Resource Dictionary using the named assembly and a relative URI and binds the Canvas to a MenuItems Icon. This code to get the vector graphic Canvas is

public Canvas GetVectorGraphic(string assemblyName, string relativeUri, string name)
{
    var imageResourceDictionary = new ResourceDictionary();
    imageResourceDictionary.Source = 
        new Uri(assemblyName + ";component/" + relativeUri, UriKind.Relative)
            ?? new Uri(relativeUri, UriKind.Relative);
    if (imageResourceDictionary.Source == null)
        return default(Canvas);
    return imageResourceDictionary[name] as Canvas;
}

The binding to my MenuItem is

<Style x:Key="MenuItem" TargetType="{x:Type Controls:MenuItemEx}">
    <Setter Property="Icon">
        <Setter.Value>
            <Image Source="{Binding IconSource}" Width="16" Height="16"/>
        </Setter.Value>
    </Setter>
    ...

I have also attempted

<Setter Property="Icon">
    <Setter.Value>
        <Rectangle Width="16" Height="16">
            <Rectangle.Fill>
                <VisualBrush Stretch="Uniform"
                             Visual="{Binding IconSource}"/>
            </Rectangle.Fill>
        </Rectangle>
    </Setter.Value>
</Setter>

You can see I am both attempting to set the height on the Canvas to 16 x 16 directly and also by reverting to fill a sized rectangle, but this is failing - it just results in a blank image. If I leave out the Height and Width specifications the resulting graphic is too large.

I have also attempted to resize the Canvas when I read it from the dictionary via

...
Canvas c = imageResourceDictionary[name] as Canvas;
c.Height = 16; 
c.Width = 16;
return c;

But this causes the image to disappear.

How can I correctly re-size my Canvas object to fit my MenuItem?


Edit (Partial Answer):

I have come accross many many posts, but none that solved all the issues I was having in one post. The problems:

  1. Not getting any images displayed using vector graphics. [SOLVED]
  2. Getting images displayed but not correctly scaled. [SOLVED]
  3. Getting images scaled and displayed but only one at a time. [SOLVED]
  4. Imaged are scaled and displayed for all required MenuItems but know the top level menu items have an unwanted margin. [ON GOING]

How I solved all but the last issue was 1. use the correct image source for vector graphics, in this case i found that filling a rectangle did the job. 2. You can bind to the Icon object directly to get the images, but there is no real way of scaling these appropriately. 3. You can scale the images by using a rectangle, setting Width and Height and filling this with a VisualBrush, but this shars the resource among MenuItems so only one will ever show up. To get the Icons to be non-shared you have to create a static resource and set x:Shared="False". The final XAML looks like

<Rectangle x:Key="MenuItemIcon" x:Shared="False" 
           HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
           Width="16" Height="16">
    <Rectangle.Fill>
        <VisualBrush Stretch="Uniform" Visual="{Binding IconSource}"/>
    </Rectangle.Fill>
</Rectangle>

<Style x:Key="MenuItem" TargetType="{x:Type Controls:MenuItemEx}">
    <Setter Property="Icon" Value="{StaticResource MenuItemIcon}"/>
    <Setter Property="InputGestureText" Value="{Binding InputGestureText}"/>
    <Setter Property="Caliburn:Action.Target" Value="{Binding}"/>
    <Setter Property="Caliburn:Message.Attach" Value="{Binding ActionText}"/>
</Style>

Outstanding Problem

I now have the issue where the top level items are also shifted to the right (shown with red-arrow below) because the rectangle dimensions are hard coded to 16. However, binding to the Witdh and Height seems to cause the image to disappear again...

I am now going to attempt to template the menu item and set the Icon area to auto-collapse. Any other solutions are welcome...

回答1:

Viewbox can easily take care of such things by scaling the content to available size

so remove the size from the image and place the image inside a Viewbox

example

<Setter Property="Icon">
    <Setter.Value>
        <Viewbox>
            <Image Source="{Binding IconSource}" />
        </Viewbox>
    </Setter.Value>
</Setter>

if you want to restrict the available size you may perhaps apply the size on Viewbox

eg

<Viewbox Width="16" Height="16">

from MSDN: Viewbox

A Viewbox defines a content decorator that can stretch and scale a single child to fill the available space.



回答2:

Two main issues were that Icons are shared if they are not contained in an external static container (see edits to question). The way I got this all to work in the end is to use the following XAML:

<Rectangle x:Key="MenuItemIcon" x:Shared="False" 
           Visibility="{Binding IconVisibility}"
           HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
           Width="16" Height="16">
    <Rectangle.Fill>
        <VisualBrush Stretch="Uniform" Visual="{Binding IconSource}"/>
    </Rectangle.Fill>
</Rectangle>

<Style x:Key="MenuItem" TargetType="{x:Type Controls:MenuItemEx}">
    <Setter Property="Icon" Value="{StaticResource MenuItemIcon}"/> 
    <Setter Property="InputGestureText" Value="{Binding InputGestureText}"/>
    <Setter Property="Caliburn:Action.Target" Value="{Binding}"/>
    <Setter Property="Caliburn:Message.Attach" Value="{Binding ActionText}"/>
</Style>

Where the default Visibility of the MainMenuIcon is set to Visibility.Collapsed. This provides the following menu look...