在吕毅大佬的文章中已经详细介绍了什么是AppBar: WPF 使用 AppBar 将窗口停靠在桌面上,让其他程序不占用此窗口的空间(附我封装的附加属性) - walterlv

即让窗口固定在屏幕某一边,并且保证其他窗口最大化后不会覆盖AppBar占据区域(类似于Windows任务栏)。

但是在我的环境中测试时,上面的代码出现了一些问题,例如非100%缩放显示时的坐标计算异常、多窗口同时停靠时布局错乱等。所以我重写了AppBar在WPF上的实现,效果如图:

  

一、AppBar的主要申请流程

主要流程如图:

核心代码其实在于如何计算停靠窗口的位置,要点是处理好一下几个方面:

1. 修改停靠位置时用原窗口的大小计算,被动告知需要调整位置时用即时大小计算

2. 像素单位与WPF单位之间的转换

3. 小心Windows的位置建议,并排停靠时会得到负值高宽,需要手动适配对齐方式

4. 有新的AppBar加入时,窗口会被系统强制移动到工作区(WorkArea),这点我还没能找到解决方案,只能把移动窗口的命令通过Dispatcher延迟操作

二、如何使用

1.下载我封装好的库:AppBarTest/AppBarCreator.cs at master · TwilightLemon/AppBarTest (github.com)

2.  在xaml中直接设置:

<Window ...>

<local:AppBarCreator.AppBar>
<local:AppBar x:Name="appBar" Location="Top" OnFullScreenStateChanged="AppBar_OnFullScreenStateChanged"/>
</local:AppBarCreator.AppBar> ...
</Window>

或者在后台创建:

private readonly AppBar appBar=new AppBar();

...Window_Loaded...
appBar.Location = AppBarLocation.Top;
appBar.OnFullScreenStateChanged += AppBar_OnFullScreenStateChanged;
AppBarCreator.SetAppBar(this, appBar);

3. 另外你可能注意到了,这里有一个OnFullScreenStateChanged事件:该事件由AppBarMsg注册,在有窗口进入或退出全屏时触发,参数bool为true指示进入全屏。

你需要手动在事件中设置全屏模式下的行为,例如在全屏时隐藏AppBar

    private void AppBar_OnFullScreenStateChanged(object sender, bool e)
{
Debug.WriteLine("Full Screen State: "+e);
Visibility = e ? Visibility.Collapsed : Visibility.Visible;
}

我在官方的Flag上加了一个RegisterOnly,即只注册AppBarMsg而不真的停靠窗口,可以此用来作全屏模式监听。

4. 如果你需要在每个虚拟桌面都显示AppBar(像任务栏那样),可以尝试为窗口使用SetWindowLong添加WS_EX_TOOLWINDOW标签(自行查找)

以下贴出完整的代码:

  1 using System.ComponentModel;
2 using System.Diagnostics;
3 using System.Runtime.InteropServices;
4 using System.Windows;
5 using System.Windows.Interop;
6 using System.Windows.Threading;
7
8 namespace AppBarTest;
9 public static class AppBarCreator
10 {
11 public static readonly DependencyProperty AppBarProperty =
12 DependencyProperty.RegisterAttached(
13 "AppBar",
14 typeof(AppBar),
15 typeof(AppBarCreator),
16 new PropertyMetadata(null, OnAppBarChanged));
17 private static void OnAppBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
18 {
19 if (d is Window window && e.NewValue is AppBar appBar)
20 {
21 appBar.AttachedWindow = window;
22 }
23 }
24 public static void SetAppBar(Window element, AppBar value)
25 {
26 if (value == null) return;
27 element.SetValue(AppBarProperty, value);
28 }
29
30 public static AppBar GetAppBar(Window element)
31 {
32 return (AppBar)element.GetValue(AppBarProperty);
33 }
34 }
35
36 public class AppBar : DependencyObject
37 {
38 /// <summary>
39 /// 附加到的窗口
40 /// </summary>
41 public Window AttachedWindow
42 {
43 get => _window;
44 set
45 {
46 if (value == null) return;
47 _window = value;
48 _window.Closing += _window_Closing;
49 _window.LocationChanged += _window_LocationChanged;
50 //获取窗口句柄hWnd
51 var handle = new WindowInteropHelper(value).Handle;
52 if (handle == IntPtr.Zero)
53 {
54 //Win32窗口未创建
55 _window.SourceInitialized += _window_SourceInitialized;
56 }
57 else
58 {
59 _hWnd = handle;
60 CheckPending();
61 }
62 }
63 }
64
65 private void _window_LocationChanged(object? sender, EventArgs e)
66 {
67 Debug.WriteLine(_window.Title+ " LocationChanged: Top: "+_window.Top+" Left: "+_window.Left);
68 }
69
70 private void _window_Closing(object? sender, CancelEventArgs e)
71 {
72 _window.Closing -= _window_Closing;
73 if (Location != AppBarLocation.None)
74 DisableAppBar();
75 }
76
77 /// <summary>
78 /// 检查是否需要应用之前的Location更改
79 /// </summary>
80 private void CheckPending()
81 {
82 //创建AppBar时提前触发的LocationChanged
83 if (_locationChangePending)
84 {
85 _locationChangePending = false;
86 LoadAppBar(Location);
87 }
88 }
89 /// <summary>
90 /// 载入AppBar
91 /// </summary>
92 /// <param name="e"></param>
93 private void LoadAppBar(AppBarLocation e,AppBarLocation? previous=null)
94 {
95
96 if (e != AppBarLocation.None)
97 {
98 if (e == AppBarLocation.RegisterOnly)
99 {
100 //仅注册AppBarMsg
101 //如果之前注册过有效的AppBar则先注销,以还原位置
102 if (previous.HasValue && previous.Value != AppBarLocation.RegisterOnly)
103 {
104 if (previous.Value != AppBarLocation.None)
105 {
106 //由生效的AppBar转为RegisterOnly,还原为普通窗口再注册空AppBar
107 DisableAppBar();
108 }
109 RegisterAppBarMsg();
110 }
111 else
112 {
113 //之前未注册过AppBar,直接注册
114 RegisterAppBarMsg();
115 }
116 }
117 else
118 {
119 if (previous.HasValue && previous.Value != AppBarLocation.None)
120 {
121 //之前为RegisterOnly才备份窗口信息
122 if(previous.Value == AppBarLocation.RegisterOnly)
123 {
124 BackupWindowInfo();
125 }
126 SetAppBarPosition(_originalSize);
127 ForceWindowStyles();
128 }
129 else
130 EnableAppBar();
131 }
132 }
133 else
134 {
135 DisableAppBar();
136 }
137 }
138 private void _window_SourceInitialized(object? sender, EventArgs e)
139 {
140 _window.SourceInitialized -= _window_SourceInitialized;
141 _hWnd = new WindowInteropHelper(_window).Handle;
142 CheckPending();
143 }
144
145 /// <summary>
146 /// 当有窗口进入或退出全屏时触发 bool参数为true时表示全屏状态
147 /// </summary>
148 public event EventHandler<bool>? OnFullScreenStateChanged;
149 /// <summary>
150 /// 期望将AppBar停靠到的位置
151 /// </summary>
152 public AppBarLocation Location
153 {
154 get { return (AppBarLocation)GetValue(LocationProperty); }
155 set { SetValue(LocationProperty, value); }
156 }
157
158 public static readonly DependencyProperty LocationProperty =
159 DependencyProperty.Register(
160 "Location",
161 typeof(AppBarLocation), typeof(AppBar),
162 new PropertyMetadata(AppBarLocation.None, OnLocationChanged));
163
164 private bool _locationChangePending = false;
165 private static void OnLocationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
166 {
167 if (DesignerProperties.GetIsInDesignMode(d))
168 return;
169 if (d is not AppBar appBar) return;
170 if (appBar.AttachedWindow == null)
171 {
172 appBar._locationChangePending = true;
173 return;
174 }
175 appBar.LoadAppBar((AppBarLocation)e.NewValue,(AppBarLocation)e.OldValue);
176 }
177
178 private int _callbackId = 0;
179 private bool _isRegistered = false;
180 private Window _window = null;
181 private IntPtr _hWnd;
182 private WindowStyle _originalStyle;
183 private Point _originalPosition;
184 private Size _originalSize = Size.Empty;
185 private ResizeMode _originalResizeMode;
186 private bool _originalTopmost;
187 public Rect? DockedSize { get; set; } = null;
188 private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam,
189 IntPtr lParam, ref bool handled)
190 {
191 if (msg == _callbackId)
192 {
193 Debug.WriteLine(_window.Title + " AppBarMsg("+_callbackId+"): " + wParam.ToInt32() + " LParam: " + lParam.ToInt32());
194 switch (wParam.ToInt32())
195 {
196 case (int)Interop.AppBarNotify.ABN_POSCHANGED:
197 Debug.WriteLine("AppBarNotify.ABN_POSCHANGED ! "+_window.Title);
198 if (Location != AppBarLocation.RegisterOnly)
199 SetAppBarPosition(Size.Empty);
200 handled = true;
201 break;
202 case (int)Interop.AppBarNotify.ABN_FULLSCREENAPP:
203 OnFullScreenStateChanged?.Invoke(this, lParam.ToInt32() == 1);
204 handled = true;
205 break;
206 }
207 }
208 return IntPtr.Zero;
209 }
210
211 public void BackupWindowInfo()
212 {
213 _callbackId = 0;
214 DockedSize = null;
215 _originalStyle = _window.WindowStyle;
216 _originalSize = new Size(_window.ActualWidth, _window.ActualHeight);
217 _originalPosition = new Point(_window.Left, _window.Top);
218 _originalResizeMode = _window.ResizeMode;
219 _originalTopmost = _window.Topmost;
220 }
221 public void RestoreWindowInfo()
222 {
223 if (_originalSize != Size.Empty)
224 {
225 _window.WindowStyle = _originalStyle;
226 _window.ResizeMode = _originalResizeMode;
227 _window.Topmost = _originalTopmost;
228 _window.Left = _originalPosition.X;
229 _window.Top = _originalPosition.Y;
230 _window.Width = _originalSize.Width;
231 _window.Height = _originalSize.Height;
232 }
233 }
234 public void ForceWindowStyles()
235 {
236 _window.WindowStyle = WindowStyle.None;
237 _window.ResizeMode = ResizeMode.NoResize;
238 _window.Topmost = true;
239 }
240
241 public void RegisterAppBarMsg()
242 {
243 var data = new Interop.APPBARDATA();
244 data.cbSize = Marshal.SizeOf(data);
245 data.hWnd = _hWnd;
246
247 _isRegistered = true;
248 _callbackId = Interop.RegisterWindowMessage(Guid.NewGuid().ToString());
249 data.uCallbackMessage = _callbackId;
250 var success = Interop.SHAppBarMessage((int)Interop.AppBarMsg.ABM_NEW, ref data);
251 var source = HwndSource.FromHwnd(_hWnd);
252 Debug.WriteLineIf(source == null, "HwndSource is null!");
253 source?.AddHook(WndProc);
254 Debug.WriteLine(_window.Title+" RegisterAppBarMsg: " + _callbackId);
255 }
256 public void EnableAppBar()
257 {
258 if (!_isRegistered)
259 {
260 //备份窗口信息并设置窗口样式
261 BackupWindowInfo();
262 //注册成为AppBar窗口
263 RegisterAppBarMsg();
264 ForceWindowStyles();
265 }
266 //成为AppBar窗口之后(或已经是)只需要注册并移动窗口位置即可
267 SetAppBarPosition(_originalSize);
268 }
269 public void SetAppBarPosition(Size WindowSize)
270 {
271 var data = new Interop.APPBARDATA();
272 data.cbSize = Marshal.SizeOf(data);
273 data.hWnd = _hWnd;
274 data.uEdge = (int)Location;
275 data.uCallbackMessage = _callbackId;
276 Debug.WriteLine("\r\nWindow: "+_window.Title);
277
278 //获取WPF单位与像素的转换矩阵
279 var compositionTarget = PresentationSource.FromVisual(_window)?.CompositionTarget;
280 if (compositionTarget == null)
281 throw new Exception("居然获取不到CompositionTarget?!");
282 var toPixel = compositionTarget.TransformToDevice;
283 var toWpfUnit = compositionTarget.TransformFromDevice;
284
285 //窗口在屏幕的实际大小
286 if(WindowSize== Size.Empty)
287 WindowSize = new Size(_window.ActualWidth, _window.ActualHeight);
288 var actualSize = toPixel.Transform(new Vector(WindowSize.Width, WindowSize.Height));
289 //屏幕的真实像素
290 var workArea = toPixel.Transform(new Vector(SystemParameters.PrimaryScreenWidth, SystemParameters.PrimaryScreenHeight));
291 Debug.WriteLine("WorkArea Width: {0}, Height: {1}", workArea.X, workArea.Y);
292
293 if (Location is AppBarLocation.Left or AppBarLocation.Right)
294 {
295 data.rc.top = 0;
296 data.rc.bottom = (int)workArea.Y;
297 if (Location == AppBarLocation.Left)
298 {
299 data.rc.left = 0;
300 data.rc.right = (int)Math.Round(actualSize.X);
301 }
302 else
303 {
304 data.rc.right = (int)workArea.X;
305 data.rc.left = (int)workArea.X - (int)Math.Round(actualSize.X);
306 }
307 }
308 else
309 {
310 data.rc.left = 0;
311 data.rc.right = (int)workArea.X;
312 if (Location == AppBarLocation.Top)
313 {
314 data.rc.top = 0;
315 data.rc.bottom = (int)Math.Round(actualSize.Y);
316 }
317 else
318 {
319 data.rc.bottom = (int)workArea.Y;
320 data.rc.top = (int)workArea.Y - (int)Math.Round(actualSize.Y);
321 }
322 }
323 //以上生成的是四周都没有其他AppBar时的理想位置
324 //系统将自动调整位置以适应其他AppBar
325 Debug.WriteLine("Before QueryPos: Left: {0}, Top: {1}, Right: {2}, Bottom: {3}", data.rc.left, data.rc.top, data.rc.right, data.rc.bottom);
326 Interop.SHAppBarMessage((int)Interop.AppBarMsg.ABM_QUERYPOS, ref data);
327 Debug.WriteLine("After QueryPos: Left: {0}, Top: {1}, Right: {2}, Bottom: {3}", data.rc.left, data.rc.top, data.rc.right, data.rc.bottom);
328 //自定义对齐方式,确保Height和Width不会小于0
329 if (data.rc.bottom - data.rc.top < 0)
330 {
331 if (Location == AppBarLocation.Top)
332 data.rc.bottom = data.rc.top + (int)Math.Round(actualSize.Y);//上对齐
333 else if (Location == AppBarLocation.Bottom)
334 data.rc.top = data.rc.bottom - (int)Math.Round(actualSize.Y);//下对齐
335 }
336 if(data.rc.right - data.rc.left < 0)
337 {
338 if (Location == AppBarLocation.Left)
339 data.rc.right = data.rc.left + (int)Math.Round(actualSize.X);//左对齐
340 else if (Location == AppBarLocation.Right)
341 data.rc.left = data.rc.right - (int)Math.Round(actualSize.X);//右对齐
342 }
343 //调整完毕,设置为最终位置
344 Interop.SHAppBarMessage((int)Interop.AppBarMsg.ABM_SETPOS, ref data);
345 //应用到窗口
346 var location = toWpfUnit.Transform(new Point(data.rc.left, data.rc.top));
347 var dimension = toWpfUnit.Transform(new Vector(data.rc.right - data.rc.left,
348 data.rc.bottom - data.rc.top));
349 var rect = new Rect(location, new Size(dimension.X, dimension.Y));
350 DockedSize = rect;
351
352 _window.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, () =>{
353 _window.Left = rect.Left;
354 _window.Top = rect.Top;
355 _window.Width = rect.Width;
356 _window.Height = rect.Height;
357 });
358
359 Debug.WriteLine("Set {0} Left: {1} ,Top: {2}, Width: {3}, Height: {4}", _window.Title, _window.Left, _window.Top, _window.Width, _window.Height);
360 }
361 public void DisableAppBar()
362 {
363 if (_isRegistered)
364 {
365 _isRegistered = false;
366 var data = new Interop.APPBARDATA();
367 data.cbSize = Marshal.SizeOf(data);
368 data.hWnd = _hWnd;
369 data.uCallbackMessage = _callbackId;
370 Interop.SHAppBarMessage((int)Interop.AppBarMsg.ABM_REMOVE, ref data);
371 _isRegistered = false;
372 RestoreWindowInfo();
373 Debug.WriteLine(_window.Title + " DisableAppBar");
374 }
375 }
376 }
377
378 public enum AppBarLocation : int
379 {
380 Left = 0,
381 Top,
382 Right,
383 Bottom,
384 None,
385 RegisterOnly=99
386 }
387
388 internal static class Interop
389 {
390 #region Structures & Flags
391 [StructLayout(LayoutKind.Sequential)]
392 internal struct RECT
393 {
394 public int left;
395 public int top;
396 public int right;
397 public int bottom;
398 }
399
400 [StructLayout(LayoutKind.Sequential)]
401 internal struct APPBARDATA
402 {
403 public int cbSize;
404 public IntPtr hWnd;
405 public int uCallbackMessage;
406 public int uEdge;
407 public RECT rc;
408 public IntPtr lParam;
409 }
410
411 internal enum AppBarMsg : int
412 {
413 ABM_NEW = 0,
414 ABM_REMOVE,
415 ABM_QUERYPOS,
416 ABM_SETPOS,
417 ABM_GETSTATE,
418 ABM_GETTASKBARPOS,
419 ABM_ACTIVATE,
420 ABM_GETAUTOHIDEBAR,
421 ABM_SETAUTOHIDEBAR,
422 ABM_WINDOWPOSCHANGED,
423 ABM_SETSTATE
424 }
425 internal enum AppBarNotify : int
426 {
427 ABN_STATECHANGE = 0,
428 ABN_POSCHANGED,
429 ABN_FULLSCREENAPP,
430 ABN_WINDOWARRANGE
431 }
432 #endregion
433
434 #region Win32 API
435 [DllImport("SHELL32", CallingConvention = CallingConvention.StdCall)]
436 internal static extern uint SHAppBarMessage(int dwMessage, ref APPBARDATA pData);
437
438 [DllImport("User32.dll", CharSet = CharSet.Auto)]
439 internal static extern int RegisterWindowMessage(string msg);
440 #endregion
441 }

三、已知问题

1.在我的github上的实例程序中,如果你将两个同进程的窗口并排叠放的话,会导致explorer和你的进程双双爆栈,windows似乎不能很好地处理这两个并排放置地窗口,一直在左右调整位置,疯狂发送ABN_POSCHANGED消息。(快去clone试试,死机了不要打我) 但是并排放置示例窗口和OneNote地Dock窗口就没有问题。

2.计算停靠窗口时,如果选择停靠位置为Bottom,则系统建议的bottom位置值会比实际的高,测试发现是任务栏窗口占据了部分空间,应该是预留给平板模式的更大图标任务栏(猜测,很不合理的设计)

自动隐藏任务栏就没有这个问题:

3. 没有实现自动隐藏AppBar,故没有处理与之相关的WM_ACTIVATE等消息,有需要的可以参考官方文档。(嘻 我懒)

参考文档:

1). SHAppBarMessage function (shellapi.h) - Win32 apps | Microsoft Learn

2). ABM_QUERYPOS message (Shellapi.h) - Win32 apps | Microsoft Learn ABM_NEW & ABM_SETPOS etc..

3). 使用应用程序桌面工具栏 - Win32 apps | Microsoft Learn

4). 判断是否有全屏程序正在运行(C#)_c# 判断程序当前窗口是否全屏如果是返回原来-CSDN博客

[打个广告] [入门AppBar的最佳实践]

看这里,如果你也需要一个高度可自定义的沉浸式顶部栏(Preview): TwilightLemon/MyToolBar: 为Surface Pro而生的顶部工具栏 支持触控和笔快捷方式 (github.com)

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名TwilightLemon和原文网址,不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

WPF使用AppBar实现窗口停靠,适配缩放、全屏响应和多窗口并列(附封装好即开即用的附加属性)的更多相关文章

  1. DirectX全屏游戏中弹出窗口(转)

    一直有人问如何在DirectX全屏游戏中弹出窗口就象金山游侠一样.我答应过要给出原码,只是一直没有时间整理,不过现在总算是弄玩了.代码不长,大致作了些注释,但愿你能看懂:)按照我的说明一步步作应该就能 ...

  2. 桌面应用(.exe)设置窗口默认最大化、全屏(electron)

    设置窗口默认最大化.全屏(electron) 一.默认最大化 win = new BrowserWindow({show: false}) win.maximize() win.show() 二.默认 ...

  3. wpf仿qq边缘自动停靠,支持多屏

    wpf完全模仿qq边缘自动隐藏功能,采用鼠标钩子获取鼠标当前状态,在通过当前鼠标的位置和点击状态来计算是否需要隐藏. 以下是实现的具体方法: 一.鼠标钩子实时获取当前鼠标的位置和点击状态 /// &l ...

  4. MFC全屏显示和多窗口动态显示的一些技巧和方法

    一.全屏 1.全屏窗口从dialogex继承,因为要处理一些东西 2.全屏代码,这样设置后尺寸不会出bug,只设置为最大值的话容易出bug //get current system resolutio ...

  5. c# 窗口API,以及全屏锁定一些tips

    this.WindowState = FormWindowState.Maximized; this.FormBorderStyle = FormBorderStyle.None; /* FormBo ...

  6. webdriver设置浏览器全屏及设置浏览器窗口为特定大小的方法

    from selenium import webdriver driver = webdriver.Chrome() #全屏 driver.maximize_window() #具体大小 driver ...

  7. QT中关于窗口全屏显示与退出全屏的实现

    近期在学习QT时遇到了很多问题这也是其中一个,个人通过在各种书籍和网络上的查阅找到了一些关于这方面的答案,希望能给大家一些帮助. 首先,在QT中对于窗口显示常用的有这么几个方法可以调用: Qt全屏显示 ...

  8. Qt全屏显示窗口、子窗口的相关函数

    Qt全屏显示函数         window.showFullScreen() Qt最大化显示函数         window.showMaximized() Qt最小化显示函数         ...

  9. QT 子窗口退出全屏

    m_pWidget代表子窗口, 子窗口显示全屏: m_pWidget->setWindowFlags(Qt::Dialog); m_pWidget->showFullScreen(); 子 ...

  10. jquery3和layui冲突导,致使用layui.layer.full弹出全屏iframe窗口时高度152px问题

    项目中使用的jquery版本是jquery-3.2.1,在使用layui弹出全屏iframe窗口时,iframe窗口顶部总是出现一个152px高的滚动窗口无法实现真正全屏,代码如下: <!DOC ...

随机推荐

  1. ObjectArx 创建一个自定义实体项目步骤

    我使用的环境是cad2018+objectarx2018+vs2015+win10.先要安装desk向导程序,用向导创建项目对于初学者来说是很方便的,然后在配置程序编译链接的环境,最后就可以写一个项目 ...

  2. ClickHouse 初步认识

    概述 Clickhouse 是分析型数据库,真正的面向列式存储,支持高维度表.它免费开源.具备高效的数据导入和查询性能,能达到 50M/200M 每秒.支持实时查询.支持不同功能底层存储引擎,例如:M ...

  3. c# 32位程序突破2G内存限制

    起因在开发过程中,由于某些COM组件只能在32位程序下运行,程序不得不在X86平台下生成.而X86的32位程序默认内存大小被限制在2G.由于程序中可能存在大数量处理,期间对象若没有及时释放或则回收,内 ...

  4. WPF开发快速入门【6】下拉框与枚举类型

    概述 本文讲述下拉框和枚举类型进行绑定的一些操作. 下拉框的基本操作 设计部分: <ComboBox ItemsSource="{Binding Fruits}" Selec ...

  5. linux开机出现initramfs无法进入系统

    linux开机出现initramfs无法进入系统 开机后进入initramfs模式,无法进入系统时不要慌: 想一想自己根分区的文件系统名是什么,有的人的是/dev/sda1,有的人的是/dev/sda ...

  6. Go的接口与多态

    什么是Go的接口? 接口可以说是一种类型,可以粗略的理解为他的变量是一堆方法. 一个简单的案例: r,_ := http.Get("http://www.baidu.com") i ...

  7. sqlite3自动插入创建时间和更新时间

    最近在记录一些简单的结构化日志信息时,用到了sqlite3数据库(保存的信息比较简单,用Mysql,SQL Server,Postgres这些数据库有点小题大做). 以前开发系统时,用Mysql和Po ...

  8. redis实战技巧

    1.分析key大小 [root@db-51 ~]#redis-cli -h 10.0.0.51 -p 6380 --bigkeys # Scanning the entire keyspace to ...

  9. JavaScript模块化笔记

    JavaScript模块化笔记 一个模块就是一堆被封装到一个文件当中的代码,并使用export暴露部分代码给其他的文件.模块专注于一小部分功能并与应用的其他部分松耦合,这是因为模块间没有全局变量或共享 ...

  10. Spring AOP 中@Pointcut的用法(多个Pointcut)

    Spring AOP 中@Pointcut的用法(多个Pointcut) /** swagger切面,分开来写 **/ @Aspect @Component public class ApiOpera ...