2

I am creating a custom control for my WPF app. This is my code :

MainWindow.xaml.cs :

public sealed class MainTimer : FrameworkElement
{
    public DispatcherTimer Timer = new DispatcherTimer();

    public MainTimer()
    {
        Timer.Tick += delegate 
        {
            UpdateLayout();

            // No luck
            // Dispatcher.Invoke(delegate { },DispatcherPriority.Render);

            // InvalidateVisual();
        };
        Timer.Interval = new TimeSpan(10);
        Timer.Start();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        string mode = "12";

        Console.WriteLine("RENDERING");


        if (Application.Current.MainWindow != null)
        {
            if (mode == "24")
            {
                //drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(50,150,255,255)), null, BoundsRelativeTo(this, Application.Current.MainWindow));
                drawingContext.DrawText(new FormattedText(DateTime.Now.ToLongTimeString(), System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Microsoft PhagsPa"), 50, new SolidColorBrush(Color.FromArgb(50, 235, 255, 255))), new Point(0, -15));
            }
            else
            {
                string st = "";

                if (DateTime.Now.Hour > 12)
                {
                    st = ((DateTime.Now.Hour - 12).ToString("00") + " : " + DateTime.Now.Minute.ToString("00") + " : " + DateTime.Now.Second.ToString("00")) + " pm" ;
                }
                else
                {
                    st = ((DateTime.Now.Hour).ToString("00") + " : " + DateTime.Now.Minute.ToString("00") + " : " + DateTime.Now.Second.ToString("00")) + " am";
                }


                drawingContext.DrawText(new FormattedText(st, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Microsoft PhagsPa"), 40, new SolidColorBrush(Color.FromArgb(50, 200, 255, 255))), new Point(0, -12));
            }
        }
        //Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);

    }

    public static Rect BoundsRelativeTo(FrameworkElement element,Visual relativeTo)
    {
        return
          element.TransformToVisual(relativeTo)
                 .TransformBounds(LayoutInformation.GetLayoutSlot(element));
    }

}

MainWindow.xaml :

<Window x:Class="WpfApp1.MainWindow"
    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"
    xmlns:local="clr-namespace:WpfApp1"
    mc:Ignorable="d"
    ShowInTaskbar="False"
    AllowsTransparency="True" WindowStyle="None"  
    WindowStartupLocation="Manual"
    Left="1168"
    Top="120"
    ResizeMode="NoResize"
    WindowState="Normal"
    Title="MainWindow" Height="75.132" Width="283.621" FontFamily="Microsoft PhagsPa">
<Window.Background>
    <SolidColorBrush Opacity="0" Color="LightCyan"/>
</Window.Background>

<local:MainTimer Loaded="Grid_Loaded"/>

The problem is the message "RENDERING" should appear in the console every second, but it appears only once! I want my control re-render every second.
I tried using InvalidateVisual(), but the program uses up nearly 70%-80% of my CPU resources.
How can I re-render my control without using a lot of system resources?
Is there a method similar to "Refresh()" in WinForm?

Thanks in advance :)

Ben
  • 39
  • 1
  • 10
  • You don't seem to be doing anything in your rendering you couldn't do in XAML. Is there a reason you want to circumvent WPF and do it on your own? Setting the *value* every second and letting WPF do the UI drawing seems to be a much easier task. – nvoigt Dec 18 '17 at 08:57
  • Ok I will try to use Label to do it, but I still want to know how to update UI without lag, thank you for your comment. – Ben Dec 18 '17 at 09:00
  • BTW: your Timespan is 10 *ticks*. That's damn fast. You'd need something that could do around 60000 FPS for that. Make sure you use a constructor that takes seconds or maybe milliseconds. – nvoigt Dec 18 '17 at 09:04
  • Well,I think the problem really is the timespan, Now my program uses up nearly <5% of my CPU usage. When I was designing my old WinForms controls, I usually set my UI timer's interval to 10-100 tick, now I am embarrassed. Thank you for helping me ! – Ben Dec 18 '17 at 09:23

1 Answers1

2

Your settings make this very faint, but you can see the time if you know what to look for:

enter image description here

The following code works fine:

using System;

namespace WpfApp1
{
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Threading;

    public sealed class MainTimer : FrameworkElement
    {
        private DispatcherTimer Timer = new DispatcherTimer();

        public MainTimer()
        {
            // what you are looking for is InvalidateVisual
            this.Timer.Tick += (sender, args) => this.InvalidateVisual();

            // changed your timespan to a more appropriate value
            this.Timer.Interval = TimeSpan.FromSeconds(1);
            this.Timer.Start();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            string mode = "12";

            System.Diagnostics.Trace.WriteLine("Rendering");

            if (Application.Current.MainWindow != null)
            {
                if (mode == "24")
                {
                    drawingContext.DrawText(new FormattedText(DateTime.Now.ToLongTimeString(), System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Microsoft PhagsPa"), 50, new SolidColorBrush(Color.FromArgb(50, 235, 255, 255))), new Point(0, -15));
                }
                else
                {
                    string st = "";

                    if (DateTime.Now.Hour > 12)
                    {
                        st = ((DateTime.Now.Hour - 12).ToString("00") + " : " + DateTime.Now.Minute.ToString("00") + " : " + DateTime.Now.Second.ToString("00")) + " pm";
                    }
                    else
                    {
                        st = ((DateTime.Now.Hour).ToString("00") + " : " + DateTime.Now.Minute.ToString("00") + " : " + DateTime.Now.Second.ToString("00")) + " am";
                    }


                    drawingContext.DrawText(new FormattedText(st, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Microsoft PhagsPa"), 40, new SolidColorBrush(Color.FromArgb(50, 200, 255, 255))), new Point(0, -12));
                }
            }
        }
    }
}

With XAML:

<Window x:Class="WpfApp1.MainWindow"
        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"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:MainTimer/>
    </Grid>
</Window>

It's rendering every second.

I still think that this is not what custom rendering was meant for, you can do this in XAML with a timer changing your viewmodel value. However, if it gets more complicated, you may be interested in this other thread and the answer that details how to improve the performance with a drawing group.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • BTW: This app is used for my desktop gadget, so I set the color to transparent. Thank you for answering my question ! – Ben Dec 18 '17 at 09:31
  • If you can, you should avoid calling InvalidateVisual() -> https://stackoverflow.com/a/44426783/2122718 – marbel82 Oct 31 '21 at 20:41
  • 1
    @marbel82 Yes, I indeed linked to the same post in my answer here. See the last paragraph. – nvoigt Nov 01 '21 at 05:48