GacUI:跨平台和渲染器
https://github.com/vczh/GacUIBlog
UI库跨平台的方法无非就是每个平台写一次。而如何把更多的共同点抽取出来,尽量的减少每个平台写一次的部分,是每一个跨平台的UI库的重点之一。GacUI的设计比较直接,所有平台相关的部分被集中到了几个接口里。每一次把GacUI一直到一个新的平台,就把所有的这些接口都重新实现一遍。目前GacUI能在Windows与macOS跑起来,以后还要逐渐支持UWP、命令行以及通过WASM让他跑到浏览器上去。简单地讲,所有的系统调用都从INativeController
开始,这是一个单例。通过观察这个接口提供的各个服务,它本身就会慢慢引导你实现各种GacUI需要的与系统交互的功能,包括创建窗口等等。
其中最值得提到的是渲染的部分。实际上我并不想抽象渲染器。因为每一个渲染器的运行过程实在是大相径庭,如果为了开发简便而过度抽象,势必会牺牲某些平台的性能。不过我还是打算在2.0里推出一个2D绘图API,这组API就只是用来给app画画的,跟绘制控件本身并没有关系。设计这套架构的一个挑战,是渲染器本身也可能是跨平台的。一个平台上面可以有多个渲染器(譬如Windows上你就可以用GDI和Direct2D),一个渲染器也可能跨多个平台(譬如OpenGL)。所以为系统调用开发的代码,以及为渲染器开发的代码,势必本身也是解耦的。你不能说明明都是OpenGL,但是为Windows和Linux你都要各写一遍使用OpenGL绘制控件的代码,那就显得太愚蠢了。
设计都得从需求谈起,要讲GacUI如何把系统调用和渲染器都从实现里面隔离开,就得谈一谈GacUI是如何渲染控件的。
几次设计GUI库的尝试
第一次设计GUI库
我第一次做通用的控件库还是大一的时候。以前受到VB6以及Delphi的影响,对UI库的理解无非都是,用户创建一大堆控件接成一棵树,窗口修改大笑的时候自己去想办法布局,IO操作来了就直接dispatch到控件里,需要绘图了就从Windows地Paint函数一直调用洗澡去,每个控件自己负责自己的绘制。后来我尝试直接从Win32 API开始写app,发现也是这样的,那个时候就没怎么多想。因此那年第一次做一个UI库的时候,我也就理所当然地这么设计了,秉承着为开发游戏写library的精神,我毅然决然地使用了OpenGL,最后在做高级文字渲染的时候被劝退了。大三的时候我又不死心,又做了一遍。但是我的设计水平并没有得到提高,仅仅因为技术水平的提高而实现了更复杂的UI,于是得到了下面的东西,然后项目流产。 上面的设计有一个显著的坏处,在把Win32 API发挥到极致的.net Windows Forms里面就体现了出来,控件和布局混为一谈。最典型的就是TableLayoutPanel
控件。这是一个控件,但是这个控件是没有交互功能的,他唯一的目标就是做layout。但是因为他被建模成了一个控件,就被赋予了渲染的义务,你依然可以修改它的边框和背景颜色什么的。第二个显著的坏处,就是既然每一个控件都要负责绘制自己,那不管你选择什么渲染技术,你都要把这套API需要的所有东西都带进控件里。于是在当年的这套控件库里面,每一个控件都会有一大堆OpenGL的概念放在private成员变量里。所有的代码被混淆了起来,越写越复杂。由于我大一的时候,算法数据结构设计模式什么都还没开始接触,最后便陷入了混乱,于是项目流产。当然就算是流产了,那也是像模像样的,只是再也无法重构下去了。
这个项目留下来的遗产,就是现在GacUI里面的WinNativeWindow.cpp。有些人看着这份文件可能会问,为什么抽象了操作系统的接口,还要在里面再封装一次Win32 API。其实就是因为这份代码并不是从头写的。
第二次设计GUI库
于是一个很自然的想法,就是要把绘图的部分从控件里拿掉。想拿掉就得彻底,所有OpenGL的东西都不能出现在控件里。但是控件又必须把自己的信息都传递过去,否则就没办法绘图了。而且窗口也是特殊的,因为所有的绘图设备都是窗口创建的,而控件也仍然需要拿到这些设备才能绘制。因此当初我就做了一个实验,把使用Win32 API创建窗口的部分抽象掉。UI库需要一个本地窗口的时候,拿到的就只是一个窗口的抽象接口。
然后开始创建控件,每一个控件的皮肤也被抽象成了一个接口。譬如说按钮的皮肤,基本上就是一个被动接受信息的接口,他会不断地接受从控件来的命令,譬如更改字体啊,更改文字啊,更改颜色,更改位置啊啊。其次按钮放到不同的容器里面的时候,皮肤接口也会接到通知,这个信息用来把控件告知的位置换算成以窗口的一个角作为原点的坐标系。但是皮肤自己要绘制,其实还是要知道UI库用的是OpenGL。于是窗口的抽象接口还是留了一个后门,会暴露自己的OpenGL相关的信息。因此这种抽象是不够彻底的,说到底只是一次隔离,控件不需要关心渲染器,但是皮肤不仅要关心渲染器还要关心控件。
GacUI现在的INativeController
的第一个版本就是从那个时候开始创建的。当时的代码长这样:
class INativeController : public Interface
{
public:
virtual INativeWindow* CreateNativeWindow() = 0;
virtual void DestroyNativeWindow(INativeWindow* window) = 0;
virtual INativeWindow* GetMainWindow() = 0;
virtual void Run(INativeWindow* window) = 0;
virtual bool InstallListener(INativeControllerListener* listener) = 0;
virtual bool UninstallListener(INativeControllerListener* listener) = 0;
virtual int GetScreenCount() = 0;
virtual INativeScreen* GetScreen(int index) = 0;
virtual INativeScreen* GetScreen(INativeWindow* window) = 0;
};
再看看今天的INativeController
class INativeController : public virtual IDescriptable, public Description
{
public:
virtual INativeCallbackService* CallbackService()=0;
virtual INativeResourceService* ResourceService()=0;
virtual INativeAsyncService* AsyncService()=0;
virtual INativeClipboardService* ClipboardService()=0;
virtual INativeImageService* ImageService()=0;
virtual INativeScreenService* ScreenService()=0;
virtual INativeWindowService* WindowService()=0;
virtual INativeInputService* InputService()=0;
virtual INativeDialogService* DialogService()=0;
virtual WString GetExecutablePath()=0;
};
功能多到塞不下了,只能切割成不同的service了。不过这也为2.0打了一点基础,像UWP、WASM和命令行这样的平台,GacUI基本上只能用单独的一个窗口来host里面的假窗口。UWP不同的窗口是基于不同的线程的,一下子想要支持这个觉得有点难先跳过。而命令行和WASM都没办法创建多窗口。因此可以预见到时候可能会有一些service的实现本身就可以跨平台,这样独立出来也好。
第三次设计GUI库
后来就有了上一篇博客提到的那个模板实验,但是那个时候渲染器已经换成了GDI。OpenGL在Windows上搞文字处理还是很蛋疼,上古时代唯一简便的方法就是用GDI先生成贴图然后送进显卡里当资源。那我干嘛不直接用GDI呢?反正Windows 7的GDI也有硬件加速了。我那个时候还做了另一个实验,用GDI实现GDI+的各种骚效果,也成功了。于是项目就换成了GDI。
当时的代码有些已经不见了,只剩下了cppblog上的几幅图。看着这些图还可以感受到时代的烙印。当年开发模板实验的时候用的还是Windows Vista,于是审美也被默默地影响了,画出来变成了这样: 那个时候还写了一个测试程序,使用GDI模拟了GDI+的可以使用各种半透明Brush来填充边框和内容的设定。仔细的话可以看得出来,里面穷举了各种Pen和Brush的组合,甚至连渲染文字的时候也有用。 不过当年自己设计的那套XML创建模板+简单数据绑定的系统还是过于粗糙了,于是等到真的拿来做GUI的时候,我还是选择了用代码创建。难看是难看了点,但是反正C++有各种技巧可以让他变得“好看”。又过了一段时间,这个项目也流产了,主要原因就是仅仅把渲染器的代码隔离了,而没有隔离渲染器的知识(也就是说我还得知道底下用的是GDI否则写不出皮肤的这件事)。
这个项目不仅最终启发了GacUI现在的设计,还留下了现在GacUI里面的WinGDI.h。这份对于GDI的封装是从另一个对Win32 API封装的UI库拿过来的,改吧改吧就用在了项目里面。后来由于这份代码实在是过于古老,与GacUI的.net式命名方式格格不入,还被我改了一遍,变成了今天的样子。
第四次设计GUI库
因此最后就迎来了GacUI。GacUI在这个基础上做了一个更优秀的设计,解决了上面说的这些问题。
以前在知乎上说过我UI做了十遍,其实是因为除了这条故事线以外,以前还为了各种项目开发和封装了各种不完全自绘的UI库,积累了丰富但是并没有什么卵用的经验。
GacUI的执行过程与渲染架构
在GacUI的架构里面,控件和布局被分开成了两个层次的概念。图元也被抽象出来了。譬如说我需要一个长方形,那我就创建一个长方形的对象,附着在一个布局的对象上。控件本身只是一个view,他接受用户的输入,提供一些功能,通过操作布局和附着在布局上的图元来显示结果。这样皮肤接口的实现就被解放了,从直接绘制,变成了拼一颗布局图元树,然后根据控件的要求来修改这棵树上面的属性。至于图元本身的渲染就被隔离开了。每一个图元在创建的时候会跟系统要一个渲染对象,然后把自己的属性输入进去,而渲染需要的坐标本身则是布局来计算的。
举个例子,一个窗口有一行字和确定取消两个按钮,长这个样子: 在布局图元树里,长这个样子: 而里面的结构是这样的(简化过)
+ + SolidBorder(blue)
+---+ (3x3)
+---+
| +---- + SolidLabel(Welcome to GacUI!)
+---+
| +---+ + SolidBorder(blue)
| +---+ + SolidBackground(light blue)
| +---- + SolidLabel(OK)
+---+
+---+ + SolidBorder(blue)
+---+ + SolidBackground(light blue)
+---- + SolidLabel(Cancel)
这里面, 、
| | | 和 |
就是布局。每一种布局节点有自己的控制子节点位置的方法。一个布局节点可以附着一个图元,譬如SolidBorder
、SolidBackground
和SolidLabel
等。每一个图元代表一种图形。一个附着在布局节点上的图元,会填充整个布局节点代表的范围。SolidBorder
在一个
上,那么SolidBorder
画出来的边框就恰好是那个
所占的位置。在按钮里面,SolidBackground
要比SolidBorder
四周都紧一个像素,不然边框就被覆盖了。
一旦针对每个布局节点的设定完成了,那么以后在窗口被拖动的时候,两个按钮会永远对齐在右下角。而且窗口它自己就会“知道”,它的宽度和高度受到了那一行字以及按钮的限制,不能无限变小。当一个窗口里面的东西多起来之后,可以组合的布局就让app的实现变得非常简单。控件本身是不参与渲染的,所以这一切发生的时候,控件完全不需要知道。
而“控件本身只是一个view”的意思,就是指这个按钮接受用户的输入,然后通过皮肤控制布局图元树里面的一棵子树的这件事: 用户按下了一个鼠标,事情发生的顺序就是这样的:
用户按下了鼠标
INativeWindow的实现接收到了Windows发来的消息WM_LBUTTONDOWN,根据DPI设置变换了一下坐标系,广播了这个事件
GuiWindow负责处理事件的部件(GuiGraphicsHost)接收到了这个消息,根据坐标查找到了相关的布局对象,定点发送了这个消息
鼠标消息会从点中的布局对象开始一级一级往上传播
GuiButton由于监听了自己边框的鼠标消息,得知了这一事件的发生
对于GuiButton来说,鼠标按下了还没抬起来,状态发生了这个变化。于是这个状态由GuiButton重新发回给了ButtonTemplate
ButtonTemplate的职责就是修改布局图元树上的节点的属性,这个皮肤认为按钮被按下就应该是橘黄色的,于是修改了SolidBackground的颜色属性
布局图元树的某个一个节点的属性被改了,做了个标记,通知GacUI在适当的时候重新绘制窗口
在这个事件里,可能有其他的布局图元树的节点被修改了属性。于是在本次WM_LBUTTONDOWN结束之后,GacUI看一眼自己的标记,发现需要重绘了,于是开始启动重绘工作,并在必要的时候重新调整一下布局(举个例子,Cancel按钮的字改了,可能变得更长了,就要把OK挤到更远的地方)。
可以清晰地看出,GacUI的每一个部件的职责都是有限的,而且各个部件之间可以进行充分的沟通。
皮肤再也不需要管渲染是怎么完成的了,他要做的是控制布局图元树的一棵子树。这棵子树上所有的结构它都清楚地了解。自己接受来自控件的命令,每一个命令会有自己影响布局图元树的子树的方法。显而易见,不同的皮肤针对相同的命令会有不同的反应,譬如说修改的颜色可能不一样。
控件也不需要管皮肤长真么样子了。皮肤会告诉控件一些基本的信息,譬如说这棵子树的根节点是什么,有利于控件监听用户的输入。皮肤会把这些用户输入解读成语义上的命令,然后传达给皮肤。
控件也不需要管布局了。显然布局图元树才对布局负责。皮肤知道一个按钮应该长什么样子,所以它控制了属于按钮的这棵布局图元树的子树。而开发者知道app应该长什么样子,于是他会把控件放在他自己创建的窗口的布局图元树的节点上。于是控件就把自己皮肤的布局图元树接到了窗口的布局图元树上,于是整个app的界面变成了一棵巨大的布局图元树。Windows Forms的TableLayoutPanel
那样的设计成为了历史。
如果整个app的皮肤一套换掉了,那每一个按钮都会把新的按钮皮肤提供的布局图元树接到窗口的布局图元树上,而原来的那棵子树就光荣退役了。
当然了,窗口也有自己的皮肤,控件也可以嵌套,那都是后话了。至少我们很清晰地明白了,在GacUI里面,布局节点确定了每一个画上去的图元在整个窗口中所占的位置,而附着在一个布局节点上的图元对象则负责确定画上去的是什么东西。于是现在就进入了跨平台的话题:图元是如何最终控制GDI、Direct2D、OpenGL、CoreGraphics等等等等数不尽的渲染器,从而最终把界面绘制出来的呢?
平台抽象与渲染器抽象之间的解耦 在上面的讨论中,我们知道系统调用INativeController
的接口抽象掉了。这个接口是一个单例,熟悉设计模式的同学们都应该很清楚什么是单例。每一个平台都有一套INativeController
的实现。一个操作系统可以有很多个平台,典型的就是Windows,Win32、UWP、命令行,就是不同的东西。当我们创建一个GuiWindow
对象,这个对象就会执行GetCurrentController->WindowService()->CreateNativeWindow()
,向INativeController
要一个INativeWindow
。因为创建了一个INativeWindow
,系统里就出来一个窗口。要是没有窗口你还怎么往上面画东西呢?GuiWindow
拿到了INativeWindow
之后,就会把这个对象广播给以它为根节点的布局图元树上的每一个图元(IGuiGraphicsElement
)。于是现在每一个图元都知道他自己在哪一个INativeWindow
上了。这是第一步。
一个平台可能有多个渲染器的实现,甚至这个渲染器本身并不做渲染。譬如在我的设想中,UWP平台上的GacUI实现,渲染就是把布局图元树同步到UWP自己的那套东西上面去,并不调用绘图API。INativeController
本身就会广播时间,不仅仅有全局鼠标钩子啊计时器这样的东西,每一次INativeWindow
被创建和释放的时候,INativeController
都会广播消息。需要知道这些消息的对象,可以实现一个INativeControllerListener
,然后注册进去。渲染器本身也是一个INativeControllerListener
,每次INativeWindow
被创建的时候,就会开始为这个窗口做一些初始化。
对于GDI来说,窗口初始化就是要创建一个HBITMAP,每次需要刷新了就往这个HBITMAP上画,最后一次性刷新到窗口上去。对于Direct2D来说,窗口的初始化就是要创建一个render target,做类似的事情。OpenGL则更有趣,因为不同的平台上都有OpenGL,渲染的方法是一样的,但是初始化的方法大相径庭。于是对于OpenGL渲染器,大部分代码都可以共享,而初始化的这部分就每个平台做一次。
于是INativeWindow
被创建好了之后,对应的渲染设备也被创建好了,这样就可以从一个INativeWindow
得到渲染设备。这是第二步。
一个GacUI初始化的时候就要确定使用的渲染器。这个渲染器程序启动的时候,会为每一种图元绑定好一个图元渲染器的工厂示例。GacUI目前的实现是用图元的名字绑定的,其实不是太好,以后要改。不过尽管如此,实践表明在这种问题上那字符串做index对性能的影响微乎其微。每一个图元被(在第一不中)通知自己所依附的INativeWindow
之后,就会去要这个图元渲染器的工厂示例。工厂示例创建了一个图元渲染器,这个图元渲染器当然知道他自己负责的是什么图元,也知道他自己需要在哪个INativeWindow
上画,自然就知道应该去获得什么渲染设备了。这是第三步。
最后一步就容易了,每当GacUI觉得自己需要刷新了,就会挨个通知这些图元,这些图元就会通知自己的图元渲染器,图元渲染器就是干具体工作的地方了。
于是在这个架构里面,图元根本不需要自己跑在什么渲染器上,开发者只需要在初始化的时候说好要用什么渲染器,那么图元渲染器就会被连接到每一个图元上。图元渲染器的实现当然是每一个渲染器都会提供一份的,所以图元渲染器本身具有“现在开发者想用哪个渲染器”这样的知识,那他当然也就知道向谁要某个INativeWindow
对应的渲染设备了。至此控件与平台、图元与渲染器之间的解耦就完成了。
Win32平台下Direct2D渲染器的启动过程 我们可以看Direct2D作为例子。GacUI的开发者想要使用Direct2D渲染器在Win32平台上启动GacUI,那么典型的代码就是这样子:
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int CmdShow)
{
return SetupWindowsDirect2DRenderer();
}
void GuiMain()
{
MyWindow window;
GetApplication()->Run(&window);
}
SetupWindowsDirect2DRenderer
函数当然知道自己既Windows也Direct2D,于是他就调用WinMainDirect2D
函数。这个函数主要的功能就是创建Win32版本的INativeController
实现注册到单例里面,把Direct2DWindowsNativeControllerListener
注册到INativeController
里面,最后启动SetWindowsDirect2DObjectProvider
函数以及RendererMainDirect2D
函数。从此每当一个INativeWindow
被创建的时候,Direct2DWindowsNativeControllerListener
就会得到通知,并为每一个窗口创建一个ID2D1HwndRenderTarget
。从INativeWindow
到ID2D1HwndRenderTarget
的知识,就可以通过调用GetWindowsDirect2DObjectProvider
来获得。
RendererMainDirect2D
函数当然知道自己是Direct2D。在这个函数里面,它为每一个图元都注册了一个Direct2D相关的图元渲染器工厂示例。在每一个图元被创建之后,工厂示例就会创建相应的图元渲染器。这个渲染器理所当然的就知道GetWindowsDirect2DObjectProvider
这件事。在做完了这些事情之后,GuiApplicationMain
函数就被调用了。
GuiApplicationMain
已经是平台无关的函数了,它负责统筹GacUI的各项资源,初始化好后调用GuiMain
。
GuiMain
就是用户实现的函数了。
在这个例子里面,GuiMain
创建了MyWindow
,它是GuiWindow
的子类。于是在初始化函数里面,GetCurrentController()->WindowService()->CreateNativeWindow
就被调用,于是:
WinMainDirect2D
注册进去的Windows版本的INativeController
就开始发力了,创建出了一个HWND
。
WinMainDirect2D
注册进去的Direct2DWindowsNativeControllerListener
就会被通知,创建出了一个ID2D1HwndRenderTarget
。
RendererMainDirect2D
注册进去的每一个图元渲染器工厂示例也开始发力了。一个简单的窗口上可能会有几十个图元,于是几十个图元渲染器就被挂在了布局图元树上。
然后GetApplication()->Run(&window)
被执行,窗口第一次被渲染,这几十个图元渲染器纷纷调用GetWindowsDirect2DObjectProvider
之后,通过它提供的函数获取到了相应的ID2D1HwndRenderTarget
对象,开始画图。
一个GacUI窗口就显示在了用户的面前。
尾声 UI对于系统的功能需求是稳定的,GacUI在把GDI和Direct2D渲染器写好之后,INativeController
这部分基本就没怎么修改了。在2014年,GacUI被darkfall同学首先移植到了macOS上,然后就去上班没有更新了。他的反馈是移植很容易,虽然INativeController
接口很浓厚的Windows的味道,但是总的来说并没有对macOS的工作造成什么障碍。在这之后的6年里,GacUI发生了巨大的变化。到了2020年,roodkcab同学把已经没法编译的iGac repo
做了一下更新,追赶到了GacUI的最新版本,发现着6年里面GacUI对系统层面的修改微乎其微,几乎所有的进度都是跨平台的。
这一定程度上表明了,GacUI的系统解耦工作是相当成功的。GacUI在设计上把对操作系统的需求隔离并最小化,于是每次一直到一个新的平台上,都不需要花多少努力就可以完成。而且INativeController
对GacUI剩余的部分是一无所知,所以移植者只需要对目标平台有足够的了解,不需要深入了解GacUI具体是怎么运转的,也不妨碍实现INativeController
的的工作。当然这个解耦也不完美,譬如GacUI实现菜单的功能需要挂一个全局的鼠标钩子,才能在用户点击桌面或者其他程序的时候把菜单关掉。这一点对Linux平台的移植造成了一些困难,使得xGac repo
依赖了让一些程序员不开心的东西(逃。以后可能考虑把菜单的实现修改掉,尽可能脱离对全局鼠标钩子的依赖。
你可能感兴趣的:(GacUI,前世今生,c++,ui)
OC语言多界面传值五大方式
Magnetic_h
ios ui 学习 objective-c 开发语言
前言在完成暑假仿写项目时,遇到了许多需要用到多界面传值的地方,这篇博客来总结一下比较常用的五种多界面传值的方式。属性传值属性传值一般用前一个界面向后一个界面传值,简单地说就是通过访问后一个视图控制器的属性来为它赋值,通过这个属性来做到从前一个界面向后一个界面传值。首先在后一个界面中定义属性@interfaceBViewController:UIViewController@propertyNSSt
UI学习——cell的复用和自定义cell
Magnetic_h
ui 学习
目录cell的复用手动(非注册)自动(注册)自定义cellcell的复用在iOS开发中,单元格复用是一种提高表格(UITableView)和集合视图(UICollectionView)滚动性能的技术。当一个UITableViewCell或UICollectionViewCell首次需要显示时,如果没有可复用的单元格,则视图会创建一个新的单元格。一旦这个单元格滚动出屏幕,它就不会被销毁。相反,它被添
element实现动态路由+面包屑
软件技术NINI
vue案例 vue.js 前端
el-breadcrumb是ElementUI组件库中的一个面包屑导航组件,它用于显示当前页面的路径,帮助用户快速理解和导航到应用的各个部分。在Vue.js项目中,如果你已经安装了ElementUI,就可以很方便地使用el-breadcrumb组件。以下是一个基本的使用示例:安装ElementUI(如果你还没有安装的话):你可以通过npm或yarn来安装ElementUI。bash复制代码npmi
c++ 的iostream 和 c++的stdio的区别和联系
黄卷青灯77
c++ 算法 开发语言 iostream stdio
在C++中,iostream和C语言的stdio.h都是用于处理输入输出的库,但它们在设计、用法和功能上有许多不同。以下是两者的区别和联系:区别1.编程风格iostream(C++风格):C++标准库中的输入输出流类库,支持面向对象的输入输出操作。典型用法是cin(输入)和cout(输出),使用>操作符来处理数据。更加类型安全,支持用户自定义类型的输入输出。#includeintmain(){in
swagger访问路径
igotyback
swagger
Swagger2.x版本访问地址:http://{ip}:{port}/{context-path}/swagger-ui.html{ip}是你的服务器IP地址。{port}是你的应用服务端口,通常为8080。{context-path}是你的应用上下文路径,如果应用部署在根路径下,则为空。Swagger3.x版本对于Swagger3.x版本(也称为OpenAPI3)访问地址:http://{ip
【JS】执行时长(100分) |思路参考+代码解析(C++)
l939035548
JS 算法 数据结构 c++
题目为了充分发挥GPU算力,需要尽可能多的将任务交给GPU执行,现在有一个任务数组,数组元素表示在这1秒内新增的任务个数且每秒都有新增任务。假设GPU最多一次执行n个任务,一次执行耗时1秒,在保证GPU不空闲情况下,最少需要多长时间执行完成。题目输入第一个参数为GPU一次最多执行的任务个数,取值范围[1,10000]第二个参数为任务数组长度,取值范围[1,10000]第三个参数为任务数组,数字范围
基于CODESYS的多轴运动控制程序框架:逻辑与运动控制分离,快速开发灵活操作
GPJnCrbBdl
python 开发语言
基于codesys开发的多轴运动控制程序框架,将逻辑与运动控制分离,将单轴控制封装成功能块,对该功能块的操作包含了所有的单轴控制(归零、点动、相对定位、绝对定位、设置当前位置、伺服模式切换等等)。程序框架由主程序按照状态调用分归零模式、手动模式、自动模式、故障模式,程序状态的跳转都已完成,只需要根据不同的工艺要求完成所需的动作即可。变量的声明、地址的规划都严格按照C++的标准定义,能帮助开发者快速
C++ | Leetcode C++题解之第409题最长回文串
Ddddddd_158
经验分享 C++ Leetcode 题解
题目:题解:classSolution{public:intlongestPalindrome(strings){unordered_mapcount;intans=0;for(charc:s)++count[c];for(autop:count){intv=p.second;ans+=v/2*2;if(v%2==1andans%2==0)++ans;}returnans;}};
C++菜鸟教程 - 从入门到精通 第二节
DreamByte
c++
一.上节课的补充(数据类型)1.前言继上节课,我们主要讲解了输入,输出和运算符,我们现在来补充一下数据类型的知识上节课遗漏了这个知识点,非常的抱歉顺便说一下,博主要上高中了,更新会慢,2-4周更新一次对了,正好赶上中秋节,小编跟大家说一句:中秋节快乐!2.int类型上节课,我们其实只用了int类型int类型,是整数类型,它们存贮的是整数,不能存小数(浮点数)定义变量的方式很简单inta;//定义一
你可能遗漏的一些C#/.NET/.NET Core知识点
追逐时光者
C# .NET DotNetGuide编程指南 c# .net .netcore microsoft
前言在这个快速发展的技术世界中,时常会有一些重要的知识点、信息或细节被忽略或遗漏。《C#/.NET/.NETCore拾遗补漏》专栏我们将探讨一些可能被忽略或遗漏的重要知识点、信息或细节,以帮助大家更全面地了解这些技术栈的特性和发展方向。拾遗补漏GitHub开源地址https://github.com/YSGStudyHards/DotNetGuide/blob/main/docs/DotNet/D
linux 发展史
种树的猴子
内核 java 操作系统 linux 大数据
linux发展史说明此前对linux认识模糊一知半解,近期通过学习将自己对于linux的发展总结一下方便大家日后的学习。那Linux是目前一款非常火热的开源操作系统,可是linux是什么时候出现的,又是因为什么样的原因被开发出来的呢。以下将对linux的发展历程进行详细的讲解。目录一、Linux发展背景二、UINIX的诞生三、UNIX的重要分支-BSD的诞生四、Minix的诞生五、GNU与Free
Java面试题精选:消息队列(二)
芒果不是芒
Java面试题精选 java kafka
一、Kafka的特性1.消息持久化:消息存储在磁盘,所以消息不会丢失2.高吞吐量:可以轻松实现单机百万级别的并发3.扩展性:扩展性强,还是动态扩展4.多客户端支持:支持多种语言(Java、C、C++、GO、)5.KafkaStreams(一个天生的流处理):在双十一或者销售大屏就会用到这种流处理。使用KafkaStreams可以快速的把销售额统计出来6.安全机制:Kafka进行生产或者消费的时候会
spring如何整合druid连接池?
惜.己
spring spring junit 数据库 java idea 后端 xml
目录spring整合druid连接池1.新建maven项目2.新建mavenModule3.导入相关依赖4.配置log4j2.xml5.配置druid.xml1)xml中如何引入properties2)下面是配置文件6.准备jdbc.propertiesJDBC配置项解释7.配置druid8.测试spring整合druid连接池1.新建maven项目打开IDE(比如IntelliJIDEA,Ecl
esp32开发快速入门 8 : MQTT 的快速入门,基于esp32实现MQTT通信
z755924843
ESP32开发快速入门 服务器 网络 运维
MQTT介绍简介MQTT(MessageQueuingTelemetryTransport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联
vue项目element-ui的table表格单元格合并
酋长哈哈
vue.js elementui javascript 前端
一、合并效果二全部代码exportdefault{name:'CellMerge',data(){return{tableData:[{id:'1',name:'王小虎',amount1:'165',amount2:'3.2',amount3:10},{id:'1',name:'王小虎',amount1:'162',amount2:'4.43',amount3:12},{id:'1',name:'
android 更改窗口的层次,浮窗开发之窗口层级
Ms.Bu
android 更改窗口的层次
最近在项目中遇到了这样的需求:需要在特定的其他应用之上悬浮自己的UI交互(拖动、输入等复杂的UI交互),和九游的浮窗类似,不过我们的比九游的体验更好,我们越过了很多授权的限制。浮窗效果很多人都知道如何去实现一个简单的浮窗,但是却很少有人去深入的研究背后的流程机制,由于项目中浮窗交互比较复杂,遇到了些坑查看了很多资料,故总结浮窗涉及到的知识点:窗口层级关系(浮窗是如何“浮”的)?浮窗有哪些限制,如何
matlab mle 优化,MLE+: Matlab Toolbox for Integrated Modeling, Control and Optimization for Buildings...
Simon Zhong
matlab mle 优化
摘要:FollowingunilateralopticnervesectioninadultPVGhoodedrat,theaxonguidancecueephrin-A2isup-regulatedincaudalbutnotrostralsuperiorcolliculus(SC)andtheEphA5receptorisdown-regulatedinaxotomisedretinalgan
C++ lambda闭包消除类成员变量
barbyQAQ
c++ c++ java 算法
原文链接:https://blog.csdn.net/qq_51470638/article/details/142151502一、背景在面向对象编程时,常常要添加类成员变量。然而类成员一旦多了之后,也会带来干扰。拿到一个类,一看成员变量好几十个,就问你怕不怕?二、解决思路可以借助函数式编程思想,来消除一些不必要的类成员变量。三、实例举个例子:classClassA{public:...intfu
TextFiled 中输入金额
宁梓茞
要求:输入的金额不能超过六位,小数点后面只能输入两位小数如果textFIled中第一位输入的是0,后面必须输入小数点,否则禁止输入用到textfiled代理方法#pragmamark----textFiledDelegate-----(BOOL)textField:(UITextField*)textFieldshouldChangeCharactersInRange:(NSRange)range
2021 CCF 非专业级别软件能力认证第一轮(CSP-J1)入门级C++语言试题 (第三大题:完善程序 代码)
mmz1207
c++ csp
最近有一段时间没更新了,在准备CSP考试,请大家见谅。(1)有n个人围成一个圈,依次标号0到n-1。从0号开始,依次0,1,0,1...交替报数,报到一的人离开,直至圈中剩最后一个人。求最后剩下的人的编号。#includeusingnamespacestd;intf[1000010];intmain(){intn;cin>>n;inti=0,cnt=0,p=0;while(cnt#includeu
《 C++ 修炼全景指南:九 》打破编程瓶颈!掌握二叉搜索树的高效实现与技巧
Lenyiin
C++ 修炼全景指南 技术指南 c++ 算法 stl
摘要本文详细探讨了二叉搜索树(BinarySearchTree,BST)的核心概念和技术细节,包括插入、查找、删除、遍历等基本操作,并结合实际代码演示了如何实现这些功能。文章深入分析了二叉搜索树的性能优势及其时间复杂度,同时介绍了前驱、后继的查找方法等高级功能。通过自定义实现的二叉搜索树类,读者能够掌握其实际应用,此外,文章还建议进一步扩展为平衡树(如AVL树、红黑树)以优化极端情况下的性能退化。
JAVA·一个简单的登录窗口
MortalTom
java 开发语言 学习
文章目录概要整体架构流程技术名词解释技术细节资源概要JavaSwing是Java基础类库的一部分,主要用于开发图形用户界面(GUI)程序整体架构流程新建项目,导入sql.jar包(链接放在了文末),编译项目并运行技术名词解释一、特点丰富的组件提供了多种可视化组件,如按钮(JButton)、文本框(JTextField)、标签(JLabel)、下拉列表(JComboBox)等,可以满足不同的界面设计
Vue( ElementUI入门、vue-cli安装)
m0_l5z
elementui vue.js
一.ElementUI入门目录:1.ElementUI入门1.1ElementUI简介1.2Vue+ElementUI安装1.3开发示例2.搭建nodejs环境2.1nodejs介绍2.2npm是什么2.3nodejs环境搭建2.3.1下载2.3.2解压2.3.3配置环境变量2.3.4配置npm全局模块路径和cache默认安装位置2.3.5修改npm镜像提高下载速度2.3.6验证安装结果3.运行n
20个新手学习c++必会的程序 输出*三角形、杨辉三角等(附代码)
X_StarX
c++ 学习 算法 大学生 开发语言 数据结构
示例1:HelloWorld#includeusingnamespacestd;intmain(){coutusingnamespacestd;intmain(){inta=5;intb=10;intsum=a+b;coutusingnamespacestd;intfactorial(intn){if(nusingnamespacestd;voidprintFibonacci(intn){intt
C++八股
Petrichorzncu
八股总结 c++ 开发语言
这里写目录标题C++内存管理C++的构造函数,复制构造函数,和析构函数深复制与浅复制:构造函数和析构函数哪个能写成虚函数,为什么?C++数据结构内存排列结构体和类占用的内存:==虚函数和虚表的原理==虚函数虚表(Vtable)虚函数和虚表的实现细节==内存泄漏==指针的工作原理函数的传值和传址new和delete与malloc和freeC++内存区域划分C++11新特性C++常见新特性==智能指针
vue + Element UI table动态合并单元格
我家媳妇儿萌哒哒
element UI vue.js 前端 javascript
一、功能需求1、根据名称相同的合并工作阶段和主要任务合并这两列,但主要任务内容一样,但要考虑主要任务一样,但工作阶段不一样的情况。(枞向合并)2、落实情况里的定量内容和定性内容值一样则合并。(横向合并)二、功能实现exportdefault{data(){return{tableData:[{name:'a',address:'1',age:'1',six:'2'},{name:'a',addre
【2022 CCF 非专业级别软件能力认证第一轮(CSP-J1)入门级 C++语言试题及解析】
汉子萌萌哒
CCF noi 算法 数据结构 c++
一、单项选择题(共15题,每题2分,共计30分;每题有且仅有一个正确选项)1.以下哪种功能没有涉及C++语言的面向对象特性支持:()。A.C++中调用printf函数B.C++中调用用户定义的类成员函数C.C++中构造一个class或structD.C++中构造来源于同一基类的多个派生类题目解析【解析】正确答案:AC++基础知识,面向对象和类有关,类又涉及父类、子类、继承、派生等关系,printf
《 C++ 修炼全景指南:十 》自平衡的艺术:深入了解 AVL 树的核心原理与实现
Lenyiin
C++ 修炼全景指南 技术指南 c++ 数据结构 stl
摘要本文深入探讨了AVL树(自平衡二叉搜索树)的概念、特点以及实现细节。我们首先介绍了AVL树的基本原理,并详细分析了其四种旋转操作,包括左旋、右旋、左右双旋和右左双旋,阐述了它们在保持树平衡中的重要作用。接着,本文从头到尾详细描述了AVL树的插入、删除和查找操作,配合完整的代码实现和详尽的注释,使读者能够全面理解这些操作的执行过程。此外,我们还提供了AVL树的遍历方法,包括中序、前序和后序遍历,
GenVisR 基因组数据可视化实战(三)
11的雾
3.genCov画每个突变位点附件的coverage,跟igv有点相似。这个操作起来很复杂,但是图还是挺有用的。可以考虑。由于我的referencegenomebuild是hg38BiocManager::install(c("TxDb.Hsapiens.UCSC.hg38.knownGene","BSgenome.Hsapiens.UCSC.hg38"))library(TxDb.Hsapien
JAVA学习笔记之23种设计模式学习
victorfreedom
Java技术 设计模式 android java 常用设计模式
博主最近买了《设计模式》这本书来学习,无奈这本书是以C++语言为基础进行说明,整个学习流程下来效率不是很高,虽然有的设计模式通俗易懂,但感觉还是没有充分的掌握了所有的设计模式。于是博主百度了一番,发现有大神写过了这方面的问题,于是博主迅速拿来学习。一、设计模式的分类总体来说设计模式分为三大类:创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。结构型模式,共七种:适配器
怎么样才能成为专业的程序员?
cocos2d-x小菜
编程 PHP
如何要想成为一名专业的程序员?仅仅会写代码是不够的。从团队合作去解决问题到版本控制,你还得具备其他关键技能的工具包。当我们询问相关的专业开发人员,那些必备的关键技能都是什么的时候,下面是我们了解到的情况。
关于如何学习代码,各种声音很多,然后很多人就被误导为成为专业开发人员懂得一门编程语言就够了?!呵呵,就像其他工作一样,光会一个技能那是远远不够的。如果你想要成为
java web开发 高并发处理
BreakingBad
java Web 并发 开发 处理 高
java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,java高负载数据) 一:高并发高负载类网站关注点之数据库 没错,首先是数据库,这是大多数应用所面临的首个SPOF。尤其是Web2.0的应用,数据库的响应是首先要解决的。 一般来说MySQL是最常用的,可能最初是一个mysql主机,当数据增加到100万以上,那么,MySQL的效能急剧下降。常用的优化措施是M-S(
mysql批量更新
ekian
mysql
mysql更新优化:
一版的更新的话都是采用update set的方式,但是如果需要批量更新的话,只能for循环的执行更新。或者采用executeBatch的方式,执行更新。无论哪种方式,性能都不见得多好。
三千多条的更新,需要3分多钟。
查询了批量更新的优化,有说replace into的方式,即:
replace into tableName(id,status) values
微软BI(3)
18289753290
微软BI SSIS
1)
Q:该列违反了完整性约束错误;已获得 OLE DB 记录。源:“Microsoft SQL Server Native Client 11.0” Hresult: 0x80004005 说明:“不能将值 NULL 插入列 'FZCHID',表 'JRB_EnterpriseCredit.dbo.QYFZCH';列不允许有 Null 值。INSERT 失败。”。
A:一般这类问题的存在是
Java中的List
g21121
java
List是一个有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
与 set 不同,列表通常允许重复
读书笔记
永夜-极光
读书笔记
1. K是一家加工厂,需要采购原材料,有A,B,C,D 4家供应商,其中A给出的价格最低,性价比最高,那么假如你是这家企业的采购经理,你会如何决策?
传统决策: A:100%订单 B,C,D:0%
&nbs
centos 安装 Codeblocks
随便小屋
codeblocks
1.安装gcc,需要c和c++两部分,默认安装下,CentOS不安装编译器的,在终端输入以下命令即可yum install gccyum install gcc-c++
2.安装gtk2-devel,因为默认已经安装了正式产品需要的支持库,但是没有安装开发所需要的文档.yum install gtk2*
3. 安装wxGTK
yum search w
23种设计模式的形象比喻
aijuans
设计模式
1、ABSTRACT FACTORY—追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅”就行了。麦当劳和肯德基就是生产鸡翅的Factory 工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:
开发管理 CheckLists
aoyouzi
开发管理 CheckLists
开发管理 CheckLists(23) -使项目组度过完整的生命周期
开发管理 CheckLists(22) -组织项目资源
开发管理 CheckLists(21) -控制项目的范围开发管理 CheckLists(20) -项目利益相关者责任开发管理 CheckLists(19) -选择合适的团队成员开发管理 CheckLists(18) -敏捷开发 Scrum Master 工作开发管理 C
js实现切换
百合不是茶
JavaScript 栏目切换
js主要功能之一就是实现页面的特效,窗体的切换可以减少页面的大小,被门户网站大量应用思路:
1,先将要显示的设置为display:bisible 否则设为none
2,设置栏目的id ,js获取栏目的id,如果id为Null就设置为显示
3,判断js获取的id名字;再设置是否显示
代码实现:
html代码:
<di
周鸿祎在360新员工入职培训上的讲话
bijian1013
感悟 项目管理 人生 职场
这篇文章也是最近偶尔看到的,考虑到原博客发布者可能将其删除等原因,也更方便个人查找,特将原文拷贝再发布的。“学东西是为自己的,不要整天以混的姿态来跟公司博弈,就算是混,我觉得你要是能在混的时间里,收获一些别的有利于人生发展的东西,也是不错的,看你怎么把握了”,看了之后,对这句话记忆犹新。 &
前端Web开发的页面效果
Bill_chen
html Web Microsoft
1.IE6下png图片的透明显示:
<img src="图片地址" border="0" style="Filter.Alpha(Opacity)=数值(100),style=数值(3)"/>
或在<head></head>间加一段JS代码让透明png图片正常显示。
2.<li>标
【JVM五】老年代垃圾回收:并发标记清理GC(CMS GC)
bit1129
垃圾回收
CMS概述
并发标记清理垃圾回收(Concurrent Mark and Sweep GC)算法的主要目标是在GC过程中,减少暂停用户线程的次数以及在不得不暂停用户线程的请夸功能,尽可能短的暂停用户线程的时间。这对于交互式应用,比如web应用来说,是非常重要的。
CMS垃圾回收针对新生代和老年代采用不同的策略。相比同吞吐量垃圾回收,它要复杂的多。吞吐量垃圾回收在执
Struts2技术总结
白糖_
struts2
必备jar文件
早在struts2.0.*的时候,struts2的必备jar包需要如下几个:
commons-logging-*.jar Apache旗下commons项目的log日志包
freemarker-*.jar
Jquery easyui layout应用注意事项
bozch
jquery 浏览器 easyui layout
在jquery easyui中提供了easyui-layout布局,他的布局比较局限,类似java中GUI的border布局。下面对其使用注意事项作简要介绍:
如果在现有的工程中前台界面均应用了jquery easyui,那么在布局的时候最好应用jquery eaysui的layout布局,否则在表单页面(编辑、查看、添加等等)在不同的浏览器会出
java-拷贝特殊链表:有一个特殊的链表,其中每个节点不但有指向下一个节点的指针pNext,还有一个指向链表中任意节点的指针pRand,如何拷贝这个特殊链表?
bylijinnan
java
public class CopySpecialLinkedList {
/**
* 题目:有一个特殊的链表,其中每个节点不但有指向下一个节点的指针pNext,还有一个指向链表中任意节点的指针pRand,如何拷贝这个特殊链表?
拷贝pNext指针非常容易,所以题目的难点是如何拷贝pRand指针。
假设原来链表为A1 -> A2 ->... -> An,新拷贝
color
Chen.H
JavaScript html css
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <HTML> <HEAD>&nbs
[信息与战争]移动通讯与网络
comsci
网络
两个坚持:手机的电池必须可以取下来
光纤不能够入户,只能够到楼宇
建议大家找这本书看看:<&
oracle flashback query(闪回查询)
daizj
oracle flashback query flashback table
在Oracle 10g中,Flash back家族分为以下成员:
Flashback Database
Flashback Drop
Flashback Table
Flashback Query(分Flashback Query,Flashback Version Query,Flashback Transaction Query)
下面介绍一下Flashback Drop 和Flas
zeus持久层DAO单元测试
deng520159
单元测试
zeus代码测试正紧张进行中,但由于工作比较忙,但速度比较慢.现在已经完成读写分离单元测试了,现在把几种情况单元测试的例子发出来,希望有人能进出意见,让它走下去.
本文是zeus的dao单元测试:
1.单元测试直接上代码
package com.dengliang.zeus.webdemo.test;
import org.junit.Test;
import o
C语言学习三printf函数和scanf函数学习
dcj3sjt126com
c printf scanf language
printf函数
/*
2013年3月10日20:42:32
地点:北京潘家园
功能:
目的:
测试%x %X %#x %#X的用法
*/
# include <stdio.h>
int main(void)
{
printf("哈哈!\n"); // \n表示换行
int i = 10;
printf
那你为什么小时候不好好读书?
dcj3sjt126com
life
dady, 我今天捡到了十块钱, 不过我还给那个人了
good girl! 那个人有没有和你讲thank you啊
没有啦....他拉我的耳朵我才把钱还给他的, 他哪里会和我讲thank you
爸爸, 如果地上有一张5块一张10块你拿哪一张呢....
当然是拿十块的咯...
爸爸你很笨的, 你不会两张都拿
爸爸为什么上个月那个人来跟你讨钱, 你告诉他没
iptables开放端口
Fanyucai
linux iptables 端口
1,找到配置文件
vi /etc/sysconfig/iptables
2,添加端口开放,增加一行,开放18081端口
-A INPUT -m state --state NEW -m tcp -p tcp --dport 18081 -j ACCEPT
3,保存
ESC
:wq!
4,重启服务
service iptables
Ehcache(05)——缓存的查询
234390216
排序 ehcache 统计 query
缓存的查询
目录
1. 使Cache可查询
1.1 基于Xml配置
1.2 基于代码的配置
2 指定可搜索的属性
2.1 可查询属性类型
2.2 &
通过hashset找到数组中重复的元素
jackyrong
hashset
如何在hashset中快速找到重复的元素呢?方法很多,下面是其中一个办法:
int[] array = {1,1,2,3,4,5,6,7,8,8};
Set<Integer> set = new HashSet<Integer>();
for(int i = 0
使用ajax和window.history.pushState无刷新改变页面内容和地址栏URL
lanrikey
history
后退时关闭当前页面
<script type="text/javascript">
jQuery(document).ready(function ($) {
if (window.history && window.history.pushState) {
应用程序的通信成本
netkiller.github.com
虚拟机 应用服务器 陈景峰 netkiller neo
应用程序的通信成本
什么是通信
一个程序中两个以上功能相互传递信号或数据叫做通信。
什么是成本
这是是指时间成本与空间成本。 时间就是传递数据所花费的时间。空间是指传递过程耗费容量大小。
都有哪些通信方式
全局变量
线程间通信
共享内存
共享文件
管道
Socket
硬件(串口,USB) 等等
全局变量
全局变量是成本最低通信方法,通过设置
一维数组与二维数组的声明与定义
恋洁e生
二维数组 一维数组 定义 声明 初始化
/** * */ package test20111005; /** * @author FlyingFire * @date:2011-11-18 上午04:33:36 * @author :代码整理 * @introduce :一维数组与二维数组的初始化 *summary: */ public c
Spring Mybatis独立事务配置
toknowme
mybatis
在项目中有很多地方会使用到独立事务,下面以获取主键为例
(1)修改配置文件spring-mybatis.xml <!-- 开启事务支持 --> <tx:annotation-driven transaction-manager="transactionManager" /> &n
更新Anadroid SDK Tooks之后,Eclipse提示No update were found
xp9802
eclipse
使用Android SDK Manager 更新了Anadroid SDK Tooks 之后,
打开eclipse提示 This Android SDK requires Android Developer Toolkit version 23.0.0 or above, 点击Check for Updates
检测一会后提示 No update were found