tags:

views:

487

answers:

1

I'm putting together a prototype to use XAML to create PNG buttons. The basic idea is that nice, gradient image buttons can be generated from localized strings instead of having them created by any sort of graphics dept.

The most straightforward example I found actually was to build a C# assembly and call it from PHP. I converted it to pure C#. Here's what my basically working prototype looks like.

The XAML file looks like:

<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Width="640" Height="480">

<Grid x:Name="LayoutRoot">
    <Button HorizontalAlignment="Left" VerticalAlignment="Top" Content="Add To Cart" FontFamily="Arial" FontSize="11" Height="24" FontWeight="Bold" Margin="0,3,0,0">
        <Button.Background>
            <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                <GradientStop Color="#FFF3F3F3" Offset="0"/>
                <GradientStop Color="#FFEBEBEB" Offset="0.5"/>
                <GradientStop Color="#FFDDDDDD" Offset="0.502"/>
                <GradientStop Color="#FFCDCDCD" Offset="1"/>
            </LinearGradientBrush>
        </Button.Background>
    </Button>
</Grid>

The Program.cs looks like:

class Program
{
    static void Main(string[] args)
    {
        FileInfo xamlFile = new FileInfo("Button.xaml");
        var inputXaml = File.ReadAllText(xamlFile.FullName);
        Thread pngCreationThread = new Thread((ThreadStart) delegate()
            {
                GenImageFromXaml(inputXaml);
            });
        pngCreationThread.IsBackground = true;
        pngCreationThread.SetApartmentState(ApartmentState.STA);
        pngCreationThread.Start();
        pngCreationThread.Join();
    }
    public static void GenImageFromXaml(string xaml)
    {
        var element = (FrameworkElement)XamlReader.Parse(xaml);
        var pngBytes = GetPngImage(element);

        using(BinaryWriter binWriter =
        new BinaryWriter(File.Open(@"output.png", FileMode.Create)))
        {
           binWriter.Write(pngBytes);
        }
    }
    private static byte[] GetPngImage(FrameworkElement element)
    {
        var size = new Size(element.Width, element.Height);
        element.Measure(size);
        element.Arrange(new Rect(size));
        var renderTarget =
          new RenderTargetBitmap((int)element.RenderSize.Width,
                                 (int)element.RenderSize.Height,
                                 96, 96,
                                 PixelFormats.Pbgra32);
        var sourceBrush = new VisualBrush(element);
        var drawingVisual = new DrawingVisual();
        using (DrawingContext drawingContext = drawingVisual.RenderOpen())
        {
            drawingContext.DrawRectangle(
                sourceBrush, null, new Rect(
                                       new Point(0, 0),
                                       new Point(element.RenderSize.Width,
                                       element.RenderSize.Height)));
        }
        renderTarget.Render(drawingVisual);
        var pngEncoder = new PngBitmapEncoder();
        pngEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
        using (var outputStream = new MemoryStream())
        {
            pngEncoder.Save(outputStream);
            return outputStream.ToArray();
        }
    }
}

What I want is XAML files that can be used as templates and the actual string in the button swapped out. My prototype works on a basic level, rendering out an image from the input XAML file.

However, the XAML file specifies the height and width explicitly. That's going to be a problem to have the string on the button itself supplied dynamically since the width won't be known in advance and any font modifications would mess with it too.

What I'm looking for is a way to have the height and width either calculated at runtime or a straightforward way to pre-calculate them based on the text string. Any ideas?

+2  A: 

You could try the follwing

1 Do not set the width and height in xaml

2 Replace you measure/arrange calls by this:

var size = new Size(double.PositiveInfinity , double.PositiveInfinity );
element.Measure(size);
element.Arrange(new Rect(element.DesiredSize));

Measure sets the DesiredSize property based on the size that you pass it, this is probably the size you want it to be.

3 (not sure if this is needed) Set your Grid (LayoutRoot) to Top/Left alignment, because Grid likes to take all the available size by default, you dont want that.

Bubblewrap
I'll try this today. I had tried leaving the width and heights out, but (as your 2nd point handles) the size calculations threw null pointers. I figured there had to be a way to measure the actual space taken up by the button and it looks like your example is exactly that.
J Wynia
I couldn't wait to try it, so did so immediately after commenting. The only thing I had to change was that the compiler didn't like double.Infinity and I had to change it to double.PositiveInfinity. It also worked without the change to the grid layout. However, being explicit about it is probably the right way to go, so I'm including that in my XAML.If you could change your answer to the PositiveInfinity field, I'll mark your answer as definitive.
J Wynia