WPF: Using AdornerDecorator to Implement Watermark

WPF: Using AdornerDecorator to Implement Watermark

Basically all code

Last updated 9/9/2021 11:55 PM
秋荷雨翔
7 min read
Category
WPF
Tags
.NET WPF Watermark

WatermarkAdorner class code:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;

namespace WPF水印装饰器
{
    /// <summary>
    /// Watermark adorner
    /// </summary>
    public class WatermarkAdorner : Adorner
    {
        private string _watermarkText;

        public WatermarkAdorner(UIElement adornedElement, string watermarkText) : base(adornedElement)
        {
            _watermarkText = watermarkText;
            this.IsHitTestVisible = false; // Make the watermark not capture events
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            Rect rect = new Rect(this.AdornedElement.RenderSize);
            double centerX = rect.Right / 2.0;
            double centerY = rect.Bottom / 2.0;

            drawingContext.PushOpacity(0.5);
            RotateTransform rotateTransform = new RotateTransform(45, centerX, centerY);
            drawingContext.PushTransform(rotateTransform);

            RotateTransform rt = new RotateTransform(-45, centerX, centerY);
            Point point = default(Point);
            double n = 5.0;
            double margin = 40;
            double halfWidth = GetTextLength(_watermarkText) * 10 / 2.0;

            // Row 1: 3 items
            point = RotatePoint(0.5, 0.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);
            point = RotatePoint(2.5, 0.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);
            point = RotatePoint(4.5, 0.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);

            // Row 2: 2 items
            point = RotatePoint(1.5, 1.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);
            point = RotatePoint(3.5, 1.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);

            // Row 3: 3 items
            point = RotatePoint(0.5, 2.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);
            point = RotatePoint(2.5, 2.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);
            point = RotatePoint(4.5, 2.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);

            // Row 4: 2 items
            point = RotatePoint(1.5, 3.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);
            point = RotatePoint(3.5, 3.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);

            // Row 5: 3 items
            point = RotatePoint(0.5, 4.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);
            point = RotatePoint(2.5, 4.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);
            point = RotatePoint(4.5, 4.5, n, rt, rect, margin);
            DrawText(point.X, point.Y, halfWidth, drawingContext, _watermarkText);

        }

        private void DrawText(double x, double y, double textHalfWidth, DrawingContext drawingContext, string text)
        {
            int fontSize = 20;
            SolidColorBrush colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#eeeef2"));
            Point point = new Point(x - textHalfWidth, y - fontSize / 2.0);
            FormattedText formattedText = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("宋体"), fontSize, colorBrush);
            drawingContext.DrawText(formattedText, point);
        }

        /// <summary>
        /// Rotate Point
        /// </summary>
        /// <param name="ratioX">Ratio of the text center point to the width of the area divided into n equal parts</param>
        /// <param name="ratioY">Ratio of the text center point to the length of the area divided into n equal parts</param>
        /// <param name="n">Divide the area width and length into n equal parts</param>
        /// <param name="rotateTransform">Rotation object</param>
        /// <param name="rect">Area</param>
        /// <param name="margin">Margin</param>
        private Point RotatePoint(double ratioX, double ratioY, double n, RotateTransform rotateTransform, Rect rect, double margin)
        {
            return rotateTransform.Transform(new Point(ratioX / n * rect.Right, ratioY / n * (rect.Bottom - 2 * margin) + margin));
        }

        #region Calculate text length (Chinese characters count as 2, uppercase letters count as 1.5, lowercase letters count as 1)
        /// <summary>
        /// Calculate text length (Chinese characters count as 2, uppercase letters count as 1.5, lowercase letters count as 1)
        /// </summary>
        private double GetTextLength(string text)
        {
            double length = 0;

            Regex reg1 = new Regex("[\u4E00-\u9FFF]|[\uFE30-\uFFA0]");
            Regex reg2 = new Regex("[A-Z]");

            foreach (char c in text)
            {
                if (reg1.IsMatch(c.ToString()))
                {
                    length += 2;
                }
                else if (reg2.IsMatch(c.ToString()))
                {
                    length += 1.5;
                }
                else
                {
                    length += 1;
                }
            }

            return length;
        }
        #endregion

    }
}

How to use:

In the Loaded method of the form or control, add the following code:

UIElement uiElement = (UIElement)this.Content;
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(uiElement);
adornerLayer.Add(new WatermarkAdorner(uiElement, _watermarkText));

Complete MainWindow.xaml code:

<Window
  x:Class="WPF水印装饰器.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:WPF水印装饰器"
  mc:Ignorable="d"
  Title="MainWindow"
  Height="1040"
  Width="1920"
  Loaded="Window_Loaded"
  WindowStyle="None"
  ResizeMode="NoResize"
  WindowStartupLocation="CenterScreen"
  MouseRightButtonDown="Window_MouseRightButtonDown"
>
  <Window.Template>
    <ControlTemplate TargetType="{x:Type Window}">
      <!-- ControlTemplate does not contain AdornerDecorator; you need to add AdornerDecorator in the ControlTemplate -->
      <AdornerDecorator>
        <ContentPresenter />
      </AdornerDecorator>
    </ControlTemplate>
  </Window.Template>
  <Window.Resources>
    <ResourceDictionary>
      <ControlTemplate x:Key="tmplBtn" TargetType="{x:Type Button}">
        <Border x:Name="border" Background="#068d6b" CornerRadius="5">
          <TextBlock
            Text="{TemplateBinding Content}"
            Foreground="#ffffff"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
          ></TextBlock>
        </Border>
        <ControlTemplate.Triggers>
          <Trigger Property="IsMouseOver" Value="true">
            <Setter
              TargetName="border"
              Property="Background"
              Value="#069d8b"
            ></Setter>
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </ResourceDictionary>
  </Window.Resources>
  <Grid x:Name="grid" Background="#094760">
    <button
      x:Name="button"
      Content="Show Child Form"
      Margin="0,0,0,0"
      Width="100"
      Height="35"
      Click="button_Click"
      Template="{StaticResource tmplBtn}"
    ></button>
    <button
      x:Name="button2"
      Content="Show Child Form 2"
      Margin="0,100,0,0"
      Width="100"
      Height="35"
      Click="button2_Click"
      Template="{StaticResource tmplBtn}"
    ></button>
  </Grid>
</Window>

Note: If the form or control uses a ControlTemplate, because ControlTemplate does not contain AdornerDecorator, you need to add AdornerDecorator in the ControlTemplate.

Complete MainWindow.xaml.cs code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPF水印装饰器
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private string _watermarkText = "Continuous R&D Test Account 34.8.99.64";

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            UIElement uiElement = (UIElement)this.Content;
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(uiElement);
            adornerLayer.Add(new WatermarkAdorner(uiElement, _watermarkText));
        }

        private void Window_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.Close();
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            Window2 win = new Window2(_watermarkText);
            win.Owner = this;
            win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            win.Show();
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
            Watermark win = new Watermark(_watermarkText);
            win.Owner = this;
            win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            win.Show();
        }
    }
}

Effect image:

Effect image

There is a PPT plugin called "iSlide", whose Windows client is developed using WPF. It has a feature called PPT collage, and the watermark function in it is similar to the code above. Let's take a look at the effect and end this article:

iSlide-PPT collage

Keep Exploring

Related Reading

More Articles
Same category / Same tag 9/13/2025

Migration Series from WPF to Avalonia: Why I Must Migrate My WPF Application to Avalonia

In the past few years, our host computer software has mainly been developed using WPF and WinForm . These technologies work well on the Windows platform and have accompanied us from small-scale trial production to the current stage of large-scale delivery. However, with business development and changes in customer requirements, the single Windows technology stack has gradually become a hurdle we must overcome.

Continue Reading
Same category / Same tag 1/26/2025

Implementing Internationalization in WPF Using Custom XML Files

This article details the method of implementing internationalization in WPF applications using custom XML files, including installing the necessary NuGet packages, dynamically retrieving the language list, dynamically switching languages, using translated strings in code and XAML interfaces, and provides a source code link to help developers easily achieve internationalization in WPF applications.

Continue Reading