为GDI+增加类似QPainter的Save和Restore功能

news2024/9/21 10:43:28

文章目录

  • 一、实现思路
    • 1、功能设计
    • 2、大体实现思路
  • 二、代码实现
    • 1、实现IList
    • 2、实现功能函数
    • 3、调用测试

原文出处: https://blog.csdn.net/haigear/article/details/129116662

在使用GDI+绘图时,不得不说QT中的QPainter有些功能是让人羡慕的,比如它的画笔笔刷颜色状态可以用去painter的save和restore功能存储起来,我们就不必要在同一段绘制代码中不停的为同样的笔刷色彩等参数进行设置,我们可以形象的称之为painter的“现场保存和复原”功能,但这些功能在GDI+绘图中也不是可望不可求,我们可以自己动手在GDI+中是实现。

一、实现思路

1、功能设计

为了说明更清楚,我们以列表的方式呈现给大家,为了称呼的方便,我们将一次绘制所使用的Rectangle,GraphicPath等绘制对象及Color,Pen,Brush为绘制样式Paintstyle:

函数名功能
Save存储当前一次绘制使用的PaintStyle,即:rectangle,path等绘制对象及color,pen,brush
Restore恢复上一次的绘制对象及色彩画笔等
getPaintStyleByName通过名称索引链表中的某个PaintStyle
getPaintStyleByIndex通过索引值索引链表中的某个PaintStyle
Clear清除所有存储PaintStyle

2、大体实现思路

新建一个实现了IList的类,专门存放pen和brush上一次的颜色大小等设置,甚至可以把rectangle的设置也一并存储下来,需要时可以随意回退。那么问有两个题来了,一是采用静态类还是非静态类?二是绘制对象(如矩形、椭圆、路径等)是否要和画笔同步,如果同步,是以绘制对象(矩形)作为主对象还是pen或brush作为主对象?
解决这个问题,其实只要看我们绘图的目标即可,绘图的目标就是绘图对象(线段,矩形,椭圆,路径等)而不是笔刷颜色样式等。所以,初步考虑以绘制对象做住对象最好,因为我们画笔的改变不会无缘无故改变,肯定是随绘制对象的要求而改变。

这里为什么要用实现了IList的类,因为我们需要存储的数据成员有多个,而且需要查询、索引和存储、恢复功能。除了恢复restore功能其余功能IList中都有,比如save功能直接用IList的Add功能即可,所以我们选择IList接口来实现。

二、代码实现

1、实现IList

首先我们来实现一个实现了IList接口的简单List实例,这里不详细介绍,直接上代码,要详细了解IList实现的同学可以参考我前面的博文《C#快速实现IList非泛型类接口的自定义类作为数据源》
实现的代码分为两部分,我这里将Ilist接口必须实现的成员和自定义方法的实现分开列出来。以下是Ilist必须实现的成员

 List<PaintStyle> stack=new List<PaintStyle>() ;
        public PaintStyle CurrentPainter=new PaintStyle("none");
        public Graphics gpainter;

        public GPainters(Graphics gp)
        {
            gpainter= gp;

        }
        #region IList成员属性
        public object? this[int index]
        {
            get { return stack[index]; }
            set { stack[index] = (Painter)value!; }
        }

        public bool IsFixedSize => throw new NotImplementedException();

        public bool IsReadOnly => throw new NotImplementedException();

        public int Count { get { return stack.Count; } }

        public bool IsSynchronized => throw new NotImplementedException();

        public object SyncRoot => throw new NotImplementedException();
        #endregion

        #region 实现IList成员函数
        public int Add(object? value)
        {
            if (value == null) throw new ArgumentNullException("value");
            else
            {
                stack.Add((PaintStyle)value);
                return stack.Count;
            }

        }

        public void Clear()
        {
            stack.Clear();
            //throw new NotImplementedException();
        }

        public bool Contains(object? value)
        {
            throw new NotImplementedException();
        }

        public void CopyTo(Array array, int index)
        {
            throw new NotImplementedException();
        }

        public IEnumerator GetEnumerator()
        {
            return stack.GetEnumerator();
            // throw new NotImplementedException();
        }

        public int IndexOf(object? value)
        {
            throw new NotImplementedException();
        }

        public void Insert(int index, object? value)
        {
            throw new NotImplementedException();
        }

        public void Remove(object? value)
        {
            stack.Remove((PaintStyle)value!);
            //throw new NotImplementedException();
        }

        public void RemoveAt(int index)
        {
            stack.RemoveAt(index);
            // throw new NotImplementedException();
        }

        #endregion

2、实现功能函数

下面是自定义方法save和restore


#region 自定义方法
   public void Save()
   {
	PaintStyle pt = new PaintStyle(CurrentPainter.Name);
	pt.StartPoint = new Point(CurrentPainter.StartPoint.X, CurrentPainter.StartPoint.Y); ;
	pt.Rect = new Rectangle(CurrentPainter.Rect.X, CurrentPainter.Rect.Y, CurrentPainter.Rect.Width, CurrentPainter.Rect.Height);
	pt.EndPoint = CurrentPainter.EndPoint;
	pt.StartPoint = CurrentPainter.EndPoint;
	pt.Rect = CurrentPainter.Rect;
	pt.EndPoint = CurrentPainter.EndPoint;
	pt.Brush= new SolidBrush(Color.RebeccaPurple);
	pt.Pen= new Pen(CurrentPainter.Pen.Color, CurrentPainter.Pen.Width);
	pt.Color = Color.FromArgb(CurrentPainter.Color.A, CurrentPainter.Color.R, CurrentPainter.Color.G, CurrentPainter.Color.B);
	Add(pt);

   }

   public void Restore()
   {

       //CurrentPainter=stack.Last();
       if (stack.Count > 0)
       {
           stack.Remove(stack.Last());
       }
       if(stack.Count>0)
           CurrentPainter = stack.Last();
   }
//	通过名称索引链表中的某个PaintStyle
public PaintStyle getPaintStyleByName
{
}
public PaintStyle getPaintStyleByIndex(int index)
{
return stack.indexof(index);
}
   #endregion

3、调用测试

如果Graphic类不是Seal类,我们可以将Graphic封装在我们新建的类,直接在书写中更方便。现在不能实现封装,我们只能通过二级类来调用绘制方法。
在这里插入图片描述
以下是在窗体paint事件中的调用代码:

GPainters gp=new GPainters(e.Graphics);          
 gp.CurrentPainter.Color = Color.Blue;
 gp.CurrentPainter.Rect = Rectangle.Inflate(ClientRectangle, -300, -200); ;
 gp.CurrentPainter.Brush = Brushes.Violet;
 gp.CurrentPainter.Pen=new Pen(Color.Red,3);
 gp.CurrentPainter.Name = "p1";
// gp.gpainter.DrawEllipse(gp.CurrentPainter.Pen, gp.CurrentPainter.Rect);
 gp.Save();          

 gp.CurrentPainter.Color = Color.Red;
 gp.CurrentPainter.Rect = new Rectangle(100,100,200,200);
 gp.CurrentPainter.Rect.Offset(50, 50);
 gp.CurrentPainter.Brush = Brushes.Yellow;
 gp.CurrentPainter.Pen = Pens.Blue;           
 gp.CurrentPainter.Name = "p2";
 gp.gpainter.DrawEllipse(gp.CurrentPainter.Pen, gp.CurrentPainter.Rect);
 gp.Save();

 gp.CurrentPainter.Color = Color.Red;
 gp.CurrentPainter.Rect = Rectangle.Inflate(ClientRectangle, -200, -100);
 gp.CurrentPainter.Brush = Brushes.Peru;
 gp.CurrentPainter.Pen = Pens.DeepPink;
 gp.gpainter.DrawEllipse(gp.CurrentPainter.Pen, gp.CurrentPainter.Rect);
 gp.CurrentPainter.Name = "other";
 gp.Save();

 gp.Restore();
 gp.Restore();
 gp.gpainter.DrawArc(gp.CurrentPainter.Pen, gp.CurrentPainter.Rect, 0, 90);

上面的代码,经过两次Restore,我们就可以回退到第一次的painterStyle,最后一次绘制的arc实际是p1的样式配置,如下图:
在这里插入图片描述

下面是我们使用的Paintstyle类的大体结构

 public class Paintstyle
    {
        Rectangle rect=new Rectangle();
        GraphicsPath path = new GraphicsPath();
        Pen pen=Pens.Red;
        Brush brush=Brushes.Blue;
        Color color=Color.Blue;
        int width=0;
        int height=0;
        Point startPoint=new Point(0,0);
        Point endPoint = new Point(0, 0);
        Point centerPoint = new Point(0, 0);
        string name = string.Empty;

        public Paintstyle(string name) { 
        this.name = name;
        }
        public Rectangle Rect { get => rect; set => rect = value; }
        public Color Color { get => color; set => color = value; }
        public Brush Brush { get => brush; set => brush = value; }
        public int Width { get => width; set => width = value; }
        public int Height { get => height; set => height = value; }
        public Point StartPoint { get => startPoint; set => startPoint = value; }
        public Point EndPoint { get => endPoint; set => endPoint = value; }
        public Point CenterPoint { get => centerPoint; set => centerPoint = value; }
        public GraphicsPath Path { get => path; set => path = value; }
        public Pen Pen { get => pen; set => pen = value; }
        public string Name { get => name; set => name = value; }

        
    }

码字不易,注明出处:https://blog.csdn.net/haigear/article/details/129116662

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/358581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java基础】泛型

泛型 generic 泛型的好处 编译器自动检查&#xff0c;减少了出错减少了转换次数&#xff0c;提高效率不再提示编译警告使程序员能够实现通用算法 定义 接口和类&#xff0c;方法都可以定义泛型 //泛型类会被在创建实例的时候被确定 // 泛型可以有多个 class Person<T,…

3分钟带您快速了解HIL测试及其架构

什么是HIL测试硬件在环&#xff08;HIL&#xff09;仿真是一种用于测试导航系统的技术&#xff0c;其中测试前并不知道车辆轨迹。在这种情况下&#xff0c;车辆轨迹被实时馈送到GNSS模拟器。HIL可用于复杂实时系统的开发和测试&#xff0c;如卫星控制系统、军事战术导弹、飞机飞…

从JDK源码中探究Runtime#exec的限制

前言 遇到很多次在调用Runtime.getRuntime().exec方法进行弹shell的时候遇到的各种限制&#xff0c;都没好好的认识认识原理&#xff0c;这次主要是总一个总结和原理上的分析。 环境搭建 之后使用docker起一个具有反序列化的漏洞的Java服务(能够执行命令就行)。 之后开启调…

深度学习神经网络基础知识(三)前向传播,反向传播和计算图

专栏&#xff1a;神经网络复现目录 深度学习神经网络基础知识(三) 本文讲述神经网络基础知识&#xff0c;具体细节讲述前向传播&#xff0c;反向传播和计算图&#xff0c;同时讲解神经网络优化方法&#xff1a;权重衰减&#xff0c;Dropout等方法&#xff0c;最后进行Kaggle实…

第47章 后端管理首页与Axios拦截守卫原理

1 404全局拦截 1.1 定义布局页&#xff1a;src\views\ 404View.vue <template> <el-container> <el-main> </el-main> <el-footer> <h1>大人&#xff0c;你要找的页面离家出走了&#xff01;小的正在努力寻找中…</h1> </el-fo…

Node多版本管理工具(轻松切换本地环境Node版本)

引言 在项目 Vue2 升级 Vue3 的过程中&#xff0c;因兼顾新老版本的项目而需不同版本的Node 环境&#xff0c;这种情况下 NVM【nodejs version manager(Node版本管理工具)】是一个很好的选择&#xff0c;它可以很方便的切换 node 环境。 安装 NVM 访问 ⬇ NVM 下载地址 &…

Android Handler的内存抖动以及子线程创建Handler

一、介绍 Handler&#xff0c;作为一个在主线程存活的消息分发工具&#xff0c;在App开发过程使用频率很高&#xff0c;也是面试问的比较多的。 面试常见的比如&#xff1a;子线程如何创建&#xff1f;Handler的机制是什么&#xff1f;内存抖动等&#xff0c;接下来我们会针对H…

Cosmos 基础教程(二)-- Run a Node, API, and CLI

有很多不同的方法来运行Cosmos区块链的节点。您将探索如何使用simapp 进行此操作。 1、编译simapp Cosmos SDK存储库包含一个名为 simapp 的文件夹。在这个文件夹中&#xff0c;您可以找到运行Cosmos SDK模拟版本的代码&#xff0c;这样您就可以在不实际与链交互的情况下测试…

从零开始使用MMSegmentation训练Segformer

从零开始使用MMSegmentation训练Segformer 写在前面&#xff1a;最新想要用最新的分割算法如&#xff1a;Segformer or SegNeXt 在自己的数据集上进行训练&#xff0c;但是有不是搞语义分割出身的&#xff0c;而且也没有系统的学过MMCV以及MMSegmentation。所以就折腾了很久&am…

Javascript 立即执行函数

IIFE,一般称为立即执行函数。你可能会问我&#xff0c;*“嘿&#xff01;我知道正常的函数表达式是什么样子的&#xff0c;但是 IIFE 到底是什么&#xff1f;”。*好吧&#xff0c;这正是我今天要在本文中回答的问题。 函数表达式 在了解立即调用函数表达式之前&#xff0c;让…

栈与队列-算法总结

目录 基础知识 用栈实现队列 用队列实现栈 栈的拿手好戏 删除字符串中的所有相邻重复项 逆波兰表达式求值 滑动窗口最大值 前k个高频元素 基础知识 栈队列stackqueue先进后出先进先出无迭代器无迭代器容器设配器容器设配器vector deque listvector deque list C标准库版本…

“dv/dt”和“di/dt”值:这些值的水平对固态继电器有什么影响?

di/dt水平过高是晶闸管故障的主要原因之一。发生这种情况时&#xff0c;施加到半导体器件上的应力会大大超过额定值并损坏功率元件。在这篇新的博客文章中&#xff0c;我们将解释dv/dt和di/dt值的重要性&#xff0c;以及为什么在为您的应用选择固态继电器之前需要考虑它们。 让…

VMware虚拟机安装Win11最详细过程以及遇到的这台电脑无法运行Windows11的问题

准备工作 在使用VMware虚拟机安装Win11之前我们先把准备工作做好&#xff0c;以免后续思绪混乱导致出错。 1. 到VMware官网或点击链接下载正版VMware Workstation 16 Pro。 2. 双击打开安装包&#xff0c;点击下一步。 3. 阅读用户许可协议&#xff0c;勾选我接受许可协议中的…

k8s-二进制部署

文章目录一、环境二、步骤1、安装cfssl工具2、部署etcd集群3、在node节点安装docker组件4、安装flannel组件部署master节点组件部署node节点部署kube-proxy组件三、测试一、环境 角色服务器地址组件master192.168.174.140kube-apiserver&#xff0c;kube-controller-manager&a…

外包出来,朋友内推我去一家公司,问的实在是太...

外包出来&#xff0c;没想到算法死在另一家厂子&#xff0c;自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到8月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有…

2023赏金计划:Coremail SRC漏洞征集与样本奖励火热进行中

赏金活动一&#xff1a;Coremail SRC漏洞奖励计划 01 活动背景 2023年1月&#xff0c;Coremail安全应急响应中心&#xff08;Coremail SRC&#xff09;正式上线启用&#xff0c;面向公众收集安全漏洞信息与安全情报。Coremail SRC旨在联合众多安全专家、白帽子研究员共同发现…

MySQL 运算符及优先级

MySQL 运算符 MySQL 主要有以下几种运算符&#xff1a; 算术运算符比较运算符逻辑运算符位运算符算术运算符 MySQL 支持的算术运算符包括: 运算符作用加法-减法*乘法/ 或 DIV除法% 或 MOD取余 在除法运算和模运算中&#xff0c;如果除数为0&#xff0c;将是非法除数&#x…

分布式事务几种方案

1&#xff09;、2PC 模式 数据库支持的 2PC【2 phase commit 二阶提交】&#xff0c;又叫做 XA Transactions。 MySQL 从 5.5 版本开始支持&#xff0c;SQL Server 2005 开始支持&#xff0c;Oracle 7 开始支持。 其中&#xff0c;XA 是一个两阶段提交协议&#xff0c;该协议…

推荐系统[八]算法实践总结V1:淘宝逛逛and阿里飞猪个性化推荐:召回算法实践总结【冷启动召回、复购召回、用户行为召回等算法实战】

0.前言:召回排序流程策略算法简介 推荐可分为以下四个流程,分别是召回、粗排、精排以及重排: 召回是源头,在某种意义上决定着整个推荐的天花板;粗排是初筛,一般不会上复杂模型;精排是整个推荐环节的重中之重,在特征和模型上都会做的比较复杂;重排,一般是做打散或满足…

Windows 服务器IIS安装,服务器多界面设置方法

当你在使用服务器时是否有遇到这样一个问题&#xff1f;当你正在服务器里进行工作时&#xff0c;突然一个小伙伴在没有告知你的情况下进入了服务器里&#xff0c;导致你服务器失去连接了&#xff0c;这种情况是非常常见的现象。 主要原因就是因为服务器没有安装多界面&#xf…