Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would I go about translating this method from Winforms to WPF?

Tags:

c#

.net

wpf

I have a simple program that creates a series of PNG images based on a range of numbers, ie; 1 through 10. I iterate through each number and create a png image of that number, in various sizes. Below is the void that I use to create the images.

private void CreatePNG(int number, string location, int width, int height)
        {
            string filename = number.ToString() + "-" + width.ToString() + "x" + height.ToString() + ".png";            
            Bitmap b = new Bitmap(width, height);

            Graphics g = Graphics.FromImage((System.Drawing.Image)b);
            g.FillRectangle(Brushes.White, 0f, 0f, width, height);

            StringFormat f = new StringFormat();
            f.Alignment = StringAlignment.Center;
            f.LineAlignment = StringAlignment.Center;
            g.DrawString(number.ToString(), new Font("Helvetica", 55), Brushes.Black, new RectangleF(0, 0, width, height), f);
            b.Save(location + "\\" + filename, ImageFormat.Png);    
        }

What I would like to do is translate this void to work with WPF. I currently have zero experience with WPF, hence my noobie question.

Target framework is 4.0

Help is much appreciated.

like image 516
Jeremy Cade Avatar asked Jan 23 '23 13:01

Jeremy Cade


1 Answers

System.Drawing is a part of WinForms NOT WPF

First I want to clear up a misunderstanding.

System.Drawing, which is based on Win32's GDI+, is really a component of WinForms. WPF is not built on it, Silverlight and other ports of WPF to other platforms will probably never include it, and there are many incompatibilities between the conventions used by System.Drawing (WinForms) and those used by System.Windows.Media (WPF). For example they use different units of measure (pixels versus fixed 96dpi), different coordinate types (int vs double), different rules for line drawing, different rotations, different brushesm, immediate vs retained mode, etc.

WPF's System.Windows.Media libraries are much more powerful than WinForms' System.Drawing (GDI+) library. WPF includes many more transforms, better opacity support, 3D, and other features that allow effects that are very difficult with System.Drawing (GDI+).

But will the code posed in the question will work with WPF anyway?

Yes it will. As long as you are running on WPF and not a clone like Silverlight, you can still access the old WinForms libraries. So you can continue to use System.Drawing calls to draw your figure. But you cannot take the resulting Bitmap and simply plop it into your WPF application: Bitmaps are incompatible and as usual WPF Bitmaps are more powerful. There are ways to convert the bitmap to a WPF bitmap, or you can save it to a standard file format like .png and reload it.

Since the code posed in the question actually does save the bitmap to a .png file, it can actually be used unchanged. But if any new WPF features are desired in creating the bitmap, or if you want to use the bitmap without saving to a file, you'll need special techniques.

So why wouldn't you want to use this code as-is?

Perhaps you would: If you're doing a quick application and already have the code written, and you really do want to store the files to disk anyway, then there is little reason to convert your code to WPF. But if you want to use new WPF features or incorporate the images directly into your WPF user interface using data binding or other dynamic techniques, you may want to convert your program to use WPF's libraries instead of using WinForms. This is a judgment call based on your needs.

Your question asked what was required to get this code to "work with WPF". The answer is: Nothing. Your code writes ordinary .png files, WPF reads ordinary .png files. So it will work.

The same thing could be said if your code was in COBOL or Java or any other primitive language calling a Unix-based drawing library from the 90s. As long as the code ends up writing a standard .png file (or a .gif or .jpg or ...) its output will work with WPF. Just as there are compelling reasons not to leave your code in COBOL or Java, there are reasons not to leave your code using System.Drawing:

  1. Dependence on System.Drawing which over time may or may not be present everywhere WPF can be used.
  2. Limitations on what you can draw
  3. Different drawing model than the rest of your WPF code

So how would I use WPF to do the job instead of WinForms?

You can make a bitmap out of anything you can display in WPF using three simple steps:

  1. Construct your WPF Visual using XAML or code.
  2. In code, construct a RenderTargetBitmap of the desired size then call .Render(visual)
  3. Either use the resulting bitmap elsewhere in your application or save it using PngBitmapEncoder or similar.

For your particular example, I might use a DataTemplate like this:

<DataTempate x:Key="NumberTemplate">
  <TextBlock
    Background="White"
    HorizontalAlignment="Center" VerticalAlignment="Center"
    FontFamily="Helvetica" FontSize="55"
    Text="{Binding}"
  />
</DataTemplate>

Then your actual code to write out the file would be:

void CreatePng(int number, string location, int width, int height)
{
  WritePng(location + "/" + number + "-" + width + "x" + height + ".png",
           new ContentPresenter  // Invokes a DataTemplate on its content
           {
             Content = number.ToString(),
             Template = NumberTemplate,
             Width = width, Height = height
           });
}

Alternatively you could omit the template and just construct the TextBlock in code in place of the ContentPresenter. But a template makes it very easy to change your look quickly and add fancy things like opacity, gradient brushes, bitmap effects, etc, so I would recommend using a template unless you're absolutely sure your look will never change.

The WritePng() method is a generic single-frame .png file writer that encapsulates WPF's advanced capabilities in a simple interface. It would be coded something like this:

void WritePng(string path, UIElement element)
{
  // Create the bitmep specifying the size, pixel format and DPI
  var bitmap = new RenderTargetBitmap((int)element.Width, (int)element.Height,
                                      96, 96, PixelFormats.Pbgra32);
  bitmap.Render(element); // At this point the bitmap is usable within WPF

  // Write to a file:  WPF can write multiple frames but we need only one
  var encoder = new PngBitmapEncoder();
  encoder.Frames.Add(BitmapFrame.Create(bitmap));
  using(Stream stream = File.Create(path))
    encoder.Save(stream);  // Could be any stream, not just a file
}
like image 157
Ray Burns Avatar answered Feb 02 '23 00:02

Ray Burns