首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

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

编程知识
2024年07月18日 15:47

在吕毅大佬的文章中已经详细介绍了什么是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和原文网址,不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

 

From:https://www.cnblogs.com/TwilightLemon/p/18309927
本文地址: http://shuzixingkong.net/article/158
0评论
提交 加载更多评论
其他文章 ComfyUI进阶:Comfyroll插件 (三)
前言: 学习ComfyUI是一场持久战,而Comfyroll 是一款功能强大的自定义节点集合,专为 ComfyUI 用户打造,旨在提供更加丰富和专业的图像生成与编辑工具。借助这些节点,用户可以在静态图像的精细调整和动态动画的复杂构建方面进行深入探索。Comfyroll 的节点设计简洁易用,功能强大,
ComfyUI进阶:Comfyroll插件 (三) ComfyUI进阶:Comfyroll插件 (三) ComfyUI进阶:Comfyroll插件 (三)
强烈推荐:18.3k star,推荐一款简单易用的HTTP请求流量录制回放工具:Goreplay
在软件开发和测试过程中,我们经常需要对应用程序的网络请求进行录制和回放,以便进行性能分析、压力测试或者模拟复杂的网络环境。今天,我要向大家推荐一款简单易用的 HTTP 请求流量录制回放工具:Goreplay。 1、简介 Goreplay 是一款用 Go 语言编写的 HTTP 请求流量录制回放工具。它
强烈推荐:18.3k star,推荐一款简单易用的HTTP请求流量录制回放工具:Goreplay 强烈推荐:18.3k star,推荐一款简单易用的HTTP请求流量录制回放工具:Goreplay
WPF/C#:实现导航功能
前言 在WPF中使用导航功能可以使用Frame控件,这是比较基础的一种方法。前几天分享了wpfui中NavigationView的基本用法,但是如果真正在项目中使用起来,基础的用法是无法满足的。今天通过wpfui中的mvvm例子来说明在wpfui中如何通过依赖注入与MVVM模式使用导航功能。实践起来
WPF/C#:实现导航功能 WPF/C#:实现导航功能 WPF/C#:实现导航功能
设计模式之适配器模式(学习笔记)
定义 适配器模式是一种结构型设计模式,它允许将一个类的接口转换为客户端希望的另一个接口。适配器使得原本由于接口不兼容而不能一起工作的类可以协同工作。通过创建适配器类,可以将现有类的接口转换成目标接口,从而使这些类能够在一起工作。 为什么使用适配器模式 兼容性 适配器模式能够解决由于接口不兼容而无法直
[MAUI 项目实战] 笔记App:程序设计
前言 有人说现在记事类app这么多,市场这么卷,为什么还想做一个笔记类App? 一来,去年小孩刚出生,需要一个可以记录喂奶时间的app,发现市面上没有一款app能够在两步内简单记录一个时间,可能iOS可以通过备忘录配合捷径做到快速记录,但是安卓上就没有类似的app。 二是,自去年做的音乐播放器以来,
[MAUI 项目实战] 笔记App:程序设计 [MAUI 项目实战] 笔记App:程序设计 [MAUI 项目实战] 笔记App:程序设计
iOS开发基础133-崩溃预防
现代移动应用的用户体验依赖于其稳定性和可靠性。然而,在开发过程中,我们时常会遇到各种崩溃问题。崩溃不仅会影响用户的使用体验,还可能损害应用的声誉。因此,本文将详细介绍一个名为CrashPrevention的工具类,它能够为iOS开发者提供多方面的崩溃预防措施,借助该工具类,开发者能够有效减少崩溃的发
Linux 提权-NFS 共享
本文通过 Google 翻译 NFS Share no_root_squash – Linux Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。 导航 0 前言 1 什么是 NFS 共享? 2 外部枚举 NFS 共享 2.1
Linux 提权-NFS 共享 Linux 提权-NFS 共享 Linux 提权-NFS 共享
反射快速入门
反射就是通过字节码文件获取类的成员变量、构造方法和成员方法的所有信息。 利用反射,我们可以获取成员变量的修饰符、名字、类型、取值。我们可以获取构造方法的名字、形参,并利用通过反射获取的构造方法创建对象。我们可以获取成员方法的修饰符、名字、形参、返回值、抛出的异常、注解,并运行通过反射获取的方法。 比
反射快速入门