0
votes

I can't find the answer on Google.

I have many strings to be presented on a canvas. Each string will be created using the FormattedText() method within a string converter called from an ItemsControl.

The problem with the code is that it needs a ridiculously large width and height in the RenderTargetBitmap() to display all the strings at different positions, even though each string has an approximate actual width of 700 and actual height of 40. (It seems as though the RenderTargetBitmap() needs to be large enough to hold not just the string, but also the position of that string from the drawing context).

How do you create an image of just a single formatted text string with the correct actual height and width of just the Formatted Text and then correctly position that image at the point of "topleft" ?

The converter called from an ItemsControl is defined as:

 public class InkConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var stringviewmodel = value as StringViewModel;
        if (stringviewmodel != null)
        {
           stringviewmodel.ft = new FormattedText(
           stringviewmodel.text,
           CultureInfo.CurrentCulture,
           FlowDirection.LeftToRight,
           new Typeface(new FontFamily("Segoe Script"), FontStyles.Italic, FontWeights.Normal, FontStretches.Normal),
           stringviewmodel.emSize,
           stringviewmodel.color);

           stringviewmodel.ft.TextAlignment = TextAlignment.Left;
           stringviewmodel.ft.LineHeight =(double)stringviewmodel.lineheight;

            Image myImage = new Image();

            DrawingVisual dw = new DrawingVisual();
            DrawingContext dc = dw.RenderOpen();
            dc.DrawText(stringviewmodel.ft, stringviewmodel.topleft);
            dc.Close();


            int Width = 2000;
            int Height = 2000;
            RenderTargetBitmap bmp = new RenderTargetBitmap(Width, Height, 120, 96, PixelFormats.Pbgra32);  

            bmp.Render(dw);
            myImage.Source = bmp;

            return myImage;
        }
        else
        {
            return null;
        }
    }

    public object  ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

}

Addendum:

I wrote this which solved the problem, but does not answer my question. (gt and lt symbols removed).

  1. Changed the itemscontrol to include:

    ItemsControl.ItemContainerStyle Style TargetType="{x:Type FrameworkElement} Setter Property="Canvas.Top" Value="{Binding topleft.Y}" Setter Property="Canvas.Left" Value="{Binding topleft.X}" Setter Property="Height" Value="{Binding ft.Height}"

  2. Removed all positioning from the Converter. The converter now reads as

    public object Convert(object value, Type targetType, object parameter,   
    System.Globalization.CultureInfo culture)
    {
        var stringviewmodel = value as StringViewModel;
        if (stringviewmodel != null)
        {
           stringviewmodel.ft = new FormattedText(
           stringviewmodel.text,
           CultureInfo.CurrentCulture,
           FlowDirection.LeftToRight,
           new Typeface(new FontFamily("Segoe Script"), FontStyles.Italic, FontWeights.Normal, FontStretches.Normal),
           stringviewmodel.emSize,
           stringviewmodel.color);
    
    
            stringviewmodel.ft.TextAlignment = TextAlignment.Left;
            stringviewmodel.ft.LineHeight =(double)stringviewmodel.lineheight;
    
            Image myImage = new Image();
            DrawingVisual dw = new DrawingVisual();
            DrawingContext dc = dw.RenderOpen();
    
            dc.DrawText(stringviewmodel.ft, new Point(0,0));
    
            dc.Close();
    
           double width = stringviewmodel.ft.text.Width - stringviewmodel.text.OverhangLeading -   stringviewmodel.text.OverhangTrailing;
    
            RenderTargetBitmap bmp = new RenderTargetBitmap(
            (int)(width,
            (int)stringviewmodel.ft.Height,
            96d,            
            96d,
            PixelFormats.Pbgra32);  
    
            bmp.Render(dw);
            myImage.Source = bmp;
    
            return myImage;
        }
        else
        {
            return null;
        }
    }
    
1
With every question you're asking here you seem to get more and more entangled in ever stranger solutions. Returning an Image control from a binding converter is just plain nonsense. Maybe it's time to give an explaination of your final goals. What is your application expected to do? There are surely much easier ways to get there. Having an ItemsControl is certainly the right thing, but then you should also create an appropriate DataTemplate (for its ItemTemplate property) to visualize your strings.Clemens
@Clemens Your're probably correct. I wrote my original code about 6 years ago to perform handwriting recognition and text editing. My newest effort is an attempt to tear the old code apart, better understand it, and enforce as close as possible a MVVM pattern. This current problem comes from taking the older formatted text, displaying it in an itemscontrol, but needing the final image per line to be as exact as possible to just the string dimensions. The resulting image will then be hit tested against. The result of strokes + hits will then be fed to my editor to modify the string.Alan Wayne
@Clemens If there is a cleaner way, I'm sure open to suggestions.Alan Wayne
Honestly, I don't understand what you're doing. What is "the older formatted text"? What is the hit testing good for? And what are "strokes + hits"? If you want to achieve a real WPF solution, you should first step back from all your details, and give a general description of the purpose or your application. What does it do? How is supposed to work?Clemens
Then why not create a UserControl that has a TextBlock and an overlayed InkCanvas, both contained in a Grid, and use that in the ItemTemplate of your ItemsControl. Thus you would have one InkCanvas for each string item and would get rid of any hit testing and all the other complicated stuff.Clemens

1 Answers

1
votes

You're living in the past with all that WinForms-looking code... this is WPF! First, you don't need any FormattedText objects as you can do this all in XAML very easily. Take this basic example:

<TextBlock Name="TextBlockToGetImageFrom">
    <Run FontFamily="Arial" FontWeight="Bold" FontSize="40" Foreground="Red" Text="W" />
    <Run FontFamily="Tahoma" Text="indows" FontSize="20" BaselineAlignment="Bottom" />
    <Run FontFamily="Arial" FontWeight="Bold" FontSize="40" Foreground="Red" Text=" P" />
    <Run FontFamily="Tahoma" Text="resentation" FontSize="20" BaselineAlignment="Center" />
    <Run FontFamily="Arial" FontWeight="Bold" FontSize="40" Foreground="Red" Text=" F" />
    <Run FontFamily="Tahoma" Text="ormat" FontSize="20" BaselineAlignment="Top" />
</TextBlock>

enter image description here

This just shows some of the flexibility of WPF and there are also far more options not shown here. So once you have your TextBlock set up as required, you can turn it into an image with just a few lines of code using the RenderTargetBitmap class:

RenderTargetBitmap renderTargetBitmap = 
    new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(TextBlockToGetImageFrom); 
PngBitmapEncoder pngImage = new PngBitmapEncoder();
pngImage.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
using (Stream fileStream = File.Create(filePath))
{
    pngImage.Save(fileStream);
}