none
Manual rendering of a WPF User Control

    Question

  • Hi, I am new to WPF (coming over from Winforms). I wanted to replace one of my winform controls with a WPF version. The rendering of this control (ie drawing rectangles, text etc) was all done in the Paint method, as opposed to using lables etc. The reason is that I want the control to be as light weith as possible (as there may be many thousands of them used in the application)

    So I created a WPF user control, and did an override for the OnRender....

    protected override void OnRender(DrawingContext drawingContext)
    {
      base.OnRender(drawingContext);
      drawingContext.DrawText(m_formattedText, m_textPos);
    }

    This text was drawn until I started adding a background (brush), to the control, then the text no longer appeared. Also, I understand the OnRender is not like the Paint where is is called everytime we want to redraw the control, so my next question would be how do I remove the previous text when I want to write something different.

    My question is, am I able to use a WPF User control in this way, and if so, how can I write out text and shapes without having to use other controls (eg labels) within the control.

    Thanks in advance for any pointers

    Saturday, May 16, 2009 9:25 AM

Answers

  • Hi,

     

    -->This text was drawn until I started adding a background (brush), to the control, then the text no longer appeared.

     

    Have a look at the default template of UserControl. then you will know what is happening here. Let’s firstly have a look at the template of UserControl:

    Template of UserControl code:
    <ControlTemplate

                TargetType="UserControl">

        <Border

                   BorderThickness="{TemplateBinding Border.BorderThickness}"

                   Padding="{TemplateBinding Control.Padding}"

                   BorderBrush="{TemplateBinding Border.BorderBrush}"

                   Background="{TemplateBinding Panel.Background}"

                   SnapsToDevicePixels="True">

            <ContentPresenter

                      Content="{TemplateBinding ContentControl.Content}"

                      ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"

                      ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"

                      HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"

                      VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"

                      SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />

        </Border>

    </ControlTemplate>

     

    When you just add the UserControl to somewhere in the Window, you will see the visual presentation normally. Then you set the Background property of the UserControl, the the Backgrond will be rendered to the Border inside the template according the following code:

    Background="{TemplateBinding Panel.Background}"

     

    Aa a result, the Border will overlap the elements that you created in the OnRender method. This is why the text is disappeared when you set the Background property for UserControl explicitly. By default, the background of Border is transparent, so you can see the text you created.

     

    -->so my next question would be how do I remove the previous text when I want to write something different.

    Based on my experience, you can not erase the text that you draw in the OnRender method directly.

    -->My question is, am I able to use a WPF User control in this way, and if so, how can I write out text and shapes without having to use other controls (eg labels) within the control.

    Yes, you can write text and shapes without using other controls. Actually, in this case, as you mentioned above you need the light-weight control. You do no need to use the UserControl and override its OnRender method. If you use thousands of such “UserControl” in your application, the UserControl still  seems a little “Larger”. You can let the class inherits from FrameworkElement base calss:
      public class CustomEllipse : FrameworkElement

        {

            protected override void OnRender(DrawingContext drawingContext)

            {

                base.OnRender(drawingContext);

                FontFamily courier = new FontFamily("Courier New");

                Typeface courierTypeface = new Typeface(courier, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);

                FormattedText ft2 = new FormattedText("Test text",

                                                     System.Globalization.CultureInfo.CurrentCulture,

                                                     System.Windows.FlowDirection.LeftToRight,

                                                     courierTypeface,

                                                     14.0,

                                                     Brushes.Black);

                drawingContext.DrawText(ft2, new Point(10, 40));

                drawingContext.DrawRectangle(null, new Pen(Brushes.Blue, 1.0), new Rect(10, 40, ft2.WidthIncludingTrailingWhitespace, ft2.Height));

            }

        }

    Hope this helps.
    Thanks.


    Jim Zhou -MSFT
    Friday, May 22, 2009 12:02 PM

All replies

  • Hi,

     

    -->This text was drawn until I started adding a background (brush), to the control, then the text no longer appeared.

     

    Have a look at the default template of UserControl. then you will know what is happening here. Let’s firstly have a look at the template of UserControl:

    Template of UserControl code:
    <ControlTemplate

                TargetType="UserControl">

        <Border

                   BorderThickness="{TemplateBinding Border.BorderThickness}"

                   Padding="{TemplateBinding Control.Padding}"

                   BorderBrush="{TemplateBinding Border.BorderBrush}"

                   Background="{TemplateBinding Panel.Background}"

                   SnapsToDevicePixels="True">

            <ContentPresenter

                      Content="{TemplateBinding ContentControl.Content}"

                      ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"

                      ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}"

                      HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}"

                      VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"

                      SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />

        </Border>

    </ControlTemplate>

     

    When you just add the UserControl to somewhere in the Window, you will see the visual presentation normally. Then you set the Background property of the UserControl, the the Backgrond will be rendered to the Border inside the template according the following code:

    Background="{TemplateBinding Panel.Background}"

     

    Aa a result, the Border will overlap the elements that you created in the OnRender method. This is why the text is disappeared when you set the Background property for UserControl explicitly. By default, the background of Border is transparent, so you can see the text you created.

     

    -->so my next question would be how do I remove the previous text when I want to write something different.

    Based on my experience, you can not erase the text that you draw in the OnRender method directly.

    -->My question is, am I able to use a WPF User control in this way, and if so, how can I write out text and shapes without having to use other controls (eg labels) within the control.

    Yes, you can write text and shapes without using other controls. Actually, in this case, as you mentioned above you need the light-weight control. You do no need to use the UserControl and override its OnRender method. If you use thousands of such “UserControl” in your application, the UserControl still  seems a little “Larger”. You can let the class inherits from FrameworkElement base calss:
      public class CustomEllipse : FrameworkElement

        {

            protected override void OnRender(DrawingContext drawingContext)

            {

                base.OnRender(drawingContext);

                FontFamily courier = new FontFamily("Courier New");

                Typeface courierTypeface = new Typeface(courier, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);

                FormattedText ft2 = new FormattedText("Test text",

                                                     System.Globalization.CultureInfo.CurrentCulture,

                                                     System.Windows.FlowDirection.LeftToRight,

                                                     courierTypeface,

                                                     14.0,

                                                     Brushes.Black);

                drawingContext.DrawText(ft2, new Point(10, 40));

                drawingContext.DrawRectangle(null, new Pen(Brushes.Blue, 1.0), new Rect(10, 40, ft2.WidthIncludingTrailingWhitespace, ft2.Height));

            }

        }

    Hope this helps.
    Thanks.


    Jim Zhou -MSFT
    Friday, May 22, 2009 12:02 PM
  • Hi Jim, thankyou very much for that information, it is most helpful.

    I suppose all I need to do now is work out how to "change" text without using a control such as a textblock or lable (if possible), doing it in the most "lightweight" was possible. For example, I would have some text that has the heading "Temperature: ", and then next to it I need to be able to draw the text for the actual temperature.

    Any ideas on the most lightweight way to do this, ie draw text, but then be able to change it? It is beginning to look like you can't draw it directly (and be able to erase and rewrite) as in GDI, so perhaps using a TextBlock is the only way? Any ideas once again greatly appretiated.

    regards
    Peter
    Wednesday, May 27, 2009 1:36 AM
  • Hi,

     

    -->Any ideas on the most lightweight way to do this, ie draw text, but then be able to change it? It is beginning to look like you can't draw it directly (and be able to erase and rewrite) as in GDI, so perhaps using a TextBlock is the only way?

     

    When your application do not need a bunches of the same light weight elements, using TextBlock makes sense. However, if you want thousands of elements to show in the UI, you could use the more light weight element that derives from FrameworkElement base class and override the OnRender method. In terms of erase and rewrite the content of the elements, you can exposes a DependencyProperty and mark the  FrameworkPropertyMetadataOptions.AffectsRender notation when register, by doing this you can impact the render effect whenever you change the value of that DependencyProperty. The following is a small example.

     

    XAML code:

    <Window x:Class="WpfDependencyPropertyTest.Window1"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:local="clr-namespace:WpfDependencyPropertyTest"

        Title="Window2" Height="300" Width="300">

        <StackPanel>

            <local:CustomeElement x:Name="customElement"   />

            <Button Content="ReWrite Text" Click="OnReWrite" Width="120" Height="30" />

            <Button Content="Erase Text" Click="OnErase" Width="120" Height="30" />

        </StackPanel>

    </Window>


    in the code behind:

    using System.Windows;

    using System.Windows.Media;

    namespace WpfDependencyPropertyTest

    {

        public partial class Window1: Window

        {

            public Window1()

            {

                InitializeComponent();

            }

            private void OnReWrite(object sender, RoutedEventArgs e)

            {

                customElement.Text = "New Text";

            }

            private void OnErase(object sender, RoutedEventArgs e)

            {

                customElement.Text = "";

            }

        }

        public class CustomeElement : FrameworkElement

        {

            public static readonly DependencyProperty TextProperty;

            static CustomeElement()

            {

                TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CustomeElement),

                    new FrameworkPropertyMetadata(

                    "Default Text"

                    ,

                    FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender | FrameworkPropertyMetadataOptions.AffectsRender

                    ));

            }

            public string Text

            {

                get { return (string)GetValue(TextProperty); }

                set { SetValue(TextProperty, value); }

            }

            protected override void OnRender(DrawingContext dc)

            {

                string text = this.Text;

                FontFamily courier = new FontFamily("Courier New");

                Typeface courierTypeface = new Typeface(courier, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);

                FormattedText formattedText = new FormattedText(text,

                                                          System.Globalization.CultureInfo.CurrentCulture,

                                                          System.Windows.FlowDirection.LeftToRight,

                                                          courierTypeface,

                                                          14.0,

                                                          Brushes.Black);

                dc.DrawText(formattedText, new Point(40, 100));

            }

        }

    }

     

    If you are still  having additional issues with this, please feel free to ask.

    Thanks.


    Jim Zhou -MSFT
    Wednesday, May 27, 2009 9:18 AM
  • It's worth bearing in mind that all controls in WPF are Windowless and thus considerably more lightweight than equivalent Windows Forms controls. It's probably better not to reinvent the wheel by creating customized versions of controls, e.g. TextBlock, unless performance analysis demonstrates that to be a significant issue with your application.
    Wednesday, May 27, 2009 12:04 PM
  • Hi Jim, thankyou once again for this valuable (and educational) information, I will check this out

    regards
    Peter
    Thursday, May 28, 2009 1:28 AM
  • Hi Andy, thanks for the input. It does appear TextBlock is designed to be lightweigtht, and may be ok to use in my situation. The winforms control I am currently using was originally made up of Labels, and once there were a large number on screen, the refresh was extremely slow, that when I replaced it all with direct GDI drawing (not to mention that had occations when ran out of user handles that max out, by default, at 10000).

    With WPF, the controls (and even the User Control) did not use up any user handles, so that solved that problem. Just wanted to make sure did the same sort of thing and make it as light weight from the start as possible. I'll do might run some tests with TextBlock vs Custom control as described by Jim above.

    regards
    Peter
    Thursday, May 28, 2009 1:35 AM