wpf,winform混合解决管理员权限无法拖放文件的问题

学习自:

https://zhuanlan.zhihu.com/p/343369663

https://zhuanlan.zhihu.com/p/48735364?from_voters_page=true

``

本文记录我解决这个问题的心路历程。总体过程是先知道了第一个文档,很好用。但融入项目的时候发现了一个BUG,而这个BUG卡了好几天怎么也解决不掉。后续准备放弃,又看到了第二个文档,解决了我的困惑,找到了问题关键。解决了BUG。

问题描述

深层次的原因,上述两个链接都提到了。简单来说就是,如果我们编写的应用程序使用了控件的拖放功能(DragDrop),当我们使用“管理员权限”启动这个应用程序时。拖放功能就失效了。

解决思路

利用Windows API中的一些方法与消息处理机制,可以越过系统默认的安全机制进行操作,如图:

关键问题

如何在wpf进行预winform窗体、控件进行消息交互

using System.Windows.Forms.Integration;

WindowsFormsHost.EnableWindowsFormsInterop();

一些辅助类与API方法

/*
* 函数原型
* LONG SetWindowLong(
[in] HWND hWnd,
[in] int nIndex,
[in] LONG dwNewLong
);
注:这个函数就使用官方文档声明不要使用的GWL_HWNDPARENT(-8)来更改窗口的父子级。但这里使用了也没出啥问题 * BOOL WINAPI ChangeWindowMessageFilterEx(
__in HWND hWnd,
__in UINT message,
__in DWORD action,
__inout_opt PCHANGEFILTERSTRUCT pChangeFilterStruct
);
注:DWORD 双字 就是4个字节。每个字节8位,就是无符号32位。在这个C#里,应该就是uint * BOOL ChangeWindowMessageFilter(
__in UINT message,
__in DWORD dwFlag
);
注:不推荐使用,主要用于兼容旧机器。现在都应该使用上面的ChangeWindowMessageFilterEx * UINT DragQueryFileA(
[in] HDROP hDrop,
[in] UINT iFile,
[out] LPSTR lpszFile,
UINT cch
);
注:关于参数iFile。
如果此参数的值为 0xFFFFFFFF,则 DragQueryFile返回放置文件的数量。
如果此参数的值介于0和放置文件总数之间,则DragQueryFile将具有相应值的文件名复制到lpszFile参数指向的缓冲区。
关于参数lpszFile:
当函数返回时,用于接收放置文件名字的缓存区地址。再通俗一点说,函数成功后,这个参数存的的就是放置文件的Name。
此文件名是一个以空字符结尾的字符串。
如果此参数为NULL,则 DragQueryFile返回此缓冲区所需的大小(以字符为单位)
关于返回值:
返回非0值,即标识调用成功。
当函数将文件名复制到缓冲区时,返回值是复制的字符数,不包括终止空字符。通俗讲就是:当lpszFile接收到了文件名,返回值就是这个文件名的字符数,不包括终止空字符。
如果索引值为 0xFFFFFFFF,则返回值为放置文件的数量。请注意,索引变量本身返回不变,因此保持为 0xFFFFFFFF。
如果索引值介于0和放置文件总数之间,并且lpszFile缓冲区地址为NULL,则返回值是缓冲区所需的大小(以字符为单位),不包括终止的空字符。 * WM_DROPFILES 消息 0x0233
当用户在已将自身注册为放置文件的接收者的应用程序的窗口上放置文件时发送。
* WM_COPYDATA 消息 0x004A
应用程序发送 WM _ COPYDATA 消息以将数据传递到其他应用程序。
* WM_COPYGLOBALDATA 消息 0x0049
从 Win3.1 开始可能与 WM_COPYDATA 有关,现在很可能从 MSDN 中删除。每个于此相关的功能还是带着这个消息。 * Marshal
提供了一个方法集合,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。
继承 Object -> Marshal
这里用到的:
Marshal.GetLastWin32Error() 返回由上一个非托管函数返回的错误代码,该函数是使用设置了 SetLastError 标志的平台调用来的。
Marshal.SizeOf(Type) 返回非托管类型的大小(以字节为单位)。 * Environment.OSVersion 可以判断当前操作系统 * MarshalAsAttribute 类
指示如何在托管代码与非托管代码之间封送数据。
继承 Object -> Attribute -> MarshalAsAttribute
* UnmanagedType 枚举
继承 Object -> ValueType -> Enum -> UnmanagedType
指定如何将参数或字段封送到非托管代码。 *Application.AddMessageFilter(System.Windows.Forms.IMessageFilter value)
value :准备加载的,接口IMessageFilter的实现
添加消息过滤器以在 Windows 消息路由到其目的地时对其进行监控。
*/

关键的接口

/*
IMessageFilter:
定义消息筛选器接口。
用它来接收,我们筛选我们所需的消息,进行处理
IDisposable:
定义一种释放分配的资源的方法。
用它来释放我们自定义的接收,处理拖放消息的类
*/
public class ElevatedDragDropManagerRevise : IMessageFilter, IDisposable

终极大坑

如图②,③是源代码中的窗体。显示,隐藏,接收拖放文件都没有问题。但当我增加了一个①的菜单文件(即从①打开②)时。第一次打开②③,功能正常。关闭②③再重新打开,拖放功能就失效了。通过把窗体Handle值打印出来可知,第二次打开的窗体其触发的拖放事件绑定的还是第一次窗体的Handle值。

通过查阅文档,我一直认为是SetWindowLong的问题。共有2个依据

/*
1.您不能使用GWL_HWNDPARENT索引调用SetWindowLong来更改子窗口的父级。而是使用SetParent函数。
2.某些窗口数据被缓存,因此您使用SetWindowLong所做的更改在调用SetWindowPos函数之前不会生效。具体来说,如果您更改任何框架样式,则必须使用SWP_FRAMECHANGED标志调用SetWindowPos才能正确更新缓存。
*/

然而尝试后,都不能解决问题。

后续,我尝试保存一个全局的固定的窗体来保存这个拖放的窗口。然而更诡异的情况发生了。当我关闭父窗体后,输出保存的全局子窗体的Handle时(没有Show),子窗体会显示但子窗体上的控件不会显示。而且,虽然全局保存了窗体,但窗体的Handle值在Closed后改变了。而且只有新建时关闭会改变,后续重新打开后不会改变。

出现这样的情况后,我一直认为是SetWindowLong的绑定没有更新或自定义事件绑定没有更新。然而尝试了各种方法,如使用其他API,父窗体的ClosedDisposed,子窗体自身的ClosedDisposed。父窗体控制子窗体,甚至子窗体通过SendMessage去控制父窗体等等等等,都不好使。大体的报错范围是,在父窗体中进行释放,可以新建接收拖放的窗口,但窗口句柄与接收放置的句柄匹配不上,触发不了后续事件。在子窗体中进行释放,父窗体中的操作会直接报错“无法访问已释放的对象”。

最后,当我放弃第一个链接的方法去寻找其他解决办法时,看到了第二个链接。发现我没有释放消息过滤器,也就是

Application.RemoveMessageFilter(this);

当我把这个加到我的工程中,一切问题就解决了。不得不让我感叹没文化可真可怕,就一句代码的问题能卡我2,3天。

一些小坑

SetWindowLong的使用

这个是在寻找解决办法的过程中发现的。因为此前我对Windows API毫无了解。所以一直以为SetWindowLong是这个绑定关系的关键,不得不说这让我走了好大的弯路。当我决心要解决这个问题,并开始逐句研究代码后。我发现这句话根本没啥作用,就算不用,也可以用父窗体中的子窗体实例来实现它的功能。

如上可知:我们使用的是SetWindowLong 标识“-8“ 的功能。也就是声明窗体的父子关系。但声明了父子关系之后会有什么样的特性呢?经过测试我在此工程中只发现以下2个功能:

  1. 绑定父子关系后,关闭、最小化父窗体,子窗体会跟着一起最小化。
  2. 绑定父子关系后,在Show之前设置子窗体的Left、Top可以生效。

我不保证正确,但我确实就发现了这2个功能。而且还蕴含着一个非常坑的问题。

即:使用SetWindowLong,或官方建议的SetParent绑定父子关系后,关闭父窗体,无法触发子窗体的Closeing,Closed事件。

使用MessageBox后程序直接卡死

这个问题出现在链接2的代码中,在控件的DragEnter事件中输出MessageBox就会直接卡死。可以解决,但我很费解是什么原因。

Close(),Dispose()与 =null

先说Close()Dispose()。这个要按照具体情况分析,可以给一个大致的分析方向。Close的含义大都是打开关闭,而Dispose的含义大都是创建释放。有时一样有时不一样。比如这里的窗体,Close后就会Dispose,不新建就无法再显示窗体。在比如比较常用的数据库连接SqlConnection connconn.Close()后,即使不新建,也可以再次使用conn.Open()继续使用。

当然在窗体这里还有一个重要区别,就是Dispose()无法触发ClosingClosed事件。

=null是另一种情况。可以这么理解,CloseDispose处理的都是内存上的实例,而=null处理的是指向内存的指针。也就是说,当你对一个对象执行Close()Dispose()后,虽然该对象的内存被释放,但它不为null,该对象依旧指向该内存。只有当你将它设为null,切断它与内存的关系。它才会变为null

这也就上述“无法访问已释放的对象”问题的根源。子窗体经由父窗体创建,父窗体保留着子窗体的实例进行操纵。此时子窗体关闭,释放了自身。但父窗体中的实例依旧指向原来的内存,不为null。但通过这个实例进行操作就会报错,因为该实例实际上已被释放。

IsDisposed

有一种冷叫你妈觉得你冷,有一种类叫做微软觉得你累

紧接上一条,微软已经把这种情况的判断标识做好了,就是IsDisposed。加入判断条件即可,非常方便。

再提一嘴,一般看到这都会想:“那我直接将对象设为null,也没有释放过,会不会占用内存越积越多,影响效率”。这个完全不用担心,因为C#有全自动的垃圾处理机制(GC)来解决这些问题。但通过这个问题的逐步排查,我也认为多了解一些内存相关的知识,对于自定义类进行主动的释放处理不失为一种好习惯。我要是之前了解过这些知识,也不会因为这么1,2行代码的问题卡了2,3天。

父子窗体

了解了3种方式

  • SetSetWindowLong 标识-8
  • SetParent
  • MdiParent

具体的特性没有过多了解。用上再说吧。

代码

Menu是目录,TestData是用来输出全局Handle的类。

MainWindow.xaml、AppManagerForm.cs、ElevatedDragDropManager.cs、_1_Form、1、2、3的Window都是链接1及相关测试代码

FileDragDropmanager.cs、FileDragDropForm、4_AnotherWindow.xaml是链接2的相关测试代码

代码里也有注释。我个人推荐第二种方法。

WPF + Winform 解决管理员权限下无法拖放文件的问题的更多相关文章

  1. 让.net程序自动运行在管理员权限下

    原文:让.net程序自动运行在管理员权限下 如何让.net程序自动运行在管理员权限下 VS2010 c# 编译的WINFORM程序 在Win7 以管理员身份运行 windows 7和vista提高的系 ...

  2. 在线上服务器上无管理员权限下升级NodeJS版本

    前言 最近发现一个线上机器的问题,是因为node版本过低导致的,线上机器的node版本还是0.x版,遂打算升级node版本. 但是发现常规的npm包的n模块无法使用,提示没有权限创建文件夹,导致nod ...

  3. C#让程序自动在管理员权限下运行

    windows 7和vista提高的系统的安全性,同时需要明确指定“以管理员身份运行”才可赋予被运行软件比较高级的权限,比如访问注册表等.否则,当以普通身份运行的程序需要访问较高级的系统资源时,将会抛 ...

  4. vs开发 winform 设置winform 获取管理员权限启动

    因为需要设置为开机项 没有管理员权限对注册表访问失败 C# 以管理员身份运行WinForm程序 转载https://www.bbsmax.com/A/obzbkKrQJE/ 鱼洛 2016-07-29 ...

  5. WCF 无管理员权限下启用服务

    1 使用 netsh.exe 工具 C:\Windows\system32>netsh http add urlacl url=http://+:8733/WcfServiceLibrary1 ...

  6. Win7如何删除需要管理员权限才能删除的文件夹

    在Windows 7系统运行中.往往会遇到想要删除某个文件夹时,系统提示:文件夹访问被拒绝 你需要权限来执行此操作,如何才能删除此类文件夹呢? ------------------ --------- ...

  7. 解决/var/log下没有messages文件的问题?

    fedora23和centos7+ 都是使用的 systemd 来代替sysv 管理系统启动和服务了. 在systemd 中主要包含两个方面的内容, 当打开/etc/inittab 文件时, 会看到: ...

  8. 实现在vista和win7中使用管理员权限接收WM_DROPFILES(OnDropFiles())消息的方法(好像XP不支持这个函数)

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #pragma once   #ifndef WM_COPYGLOBALD ...

  9. ubuntu18.04 使用管理员权限

    最近在ubuntu系统安装koa脚手架koa-generator初始化项目,报错提示要使用管理员权限执行命令. 正常情况下使用 sudo su 命令即可进入管理员权限,使用 su [ 用户名 ] 退出 ...

随机推荐

  1. springboot启动过程中常用的回调

    1.介绍 springboot提供非常丰富回调接口,利用这些接口可以做非常多的事情,对于一些常用的回调接口进行介绍 2.常用的拓展接口 1.ApplicationContextInitializer ...

  2. 使用 Jenkins + Ansible 实现 Spring Boot 自动化部署101

    本文要点:设计一条 Spring Boot 最基本的流水线:包括构建.制品上传.部署.使用 Docker 容器运行构建逻辑.自动化整个实验环境:包括 Jenkins 的配置,Jenkins agent ...

  3. elementui-日期选择器时间清空报错踩坑

    今天在项目中遇到了这个大坑 具体问题:在日期清空时会报错 解决方法:给日期绑定的值添加监听

  4. cesium 3dtiles模型单体化点击高亮效果

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. c ...

  5. java继承成员函数特点

    1 //成员函数. 2 /* 3 * 当子父类中出现成员函数一模一样的情况,会运行子类的函数. 4 * 这种现象,称为覆盖操作.这时函数在子父类中的特性. 5 * 函数两个特征: 6 * 1,重载. ...

  6. 总是记不住但又总是要用的css

    有没有经常遇到一些样式每次写都要用百度呢?我收集了一些我平时经常要用到的但又总是记不住的样式.有错误的地方欢迎指正.转载请注明出处. 一.设置input 的placeholder的字体样式 input ...

  7. 【重构前端知识体系之HTML】HTML5给网页音频带来的变化

    [重构前端知识体系之HTML]HTML5给网页音频带来的变化 引言 音乐播放,相信大家都很熟悉,但是早在之前的音乐播放之前,你的浏览器会问你,是否下载flash插件.然而现在,估计一些年轻的开发者都不 ...

  8. 微服务架构 | 5.2 基于 Sentinel 的服务限流及熔断

    目录 前言 1. Sentinel 基础知识 1.1 Sentinel 的特性 1.2 Sentinel 的组成 1.3 Sentinel 控制台上的 9 个功能 1.4 Sentinel 工作原理 ...

  9. Linux 学习2

    1.配置好阿里云yum源生成yum缓存下载nginx,并且启动nginx服务,使用浏览器访问,nginx页面 yum源的工作目录是? https://www.cnblogs.com/dlh-lmsh/ ...

  10. django之mysqlclient安装

    如果运行环境中没有安装mysqlclient,在迁移数据库时会发生错误 一.在windows下安装: ·如果直接使用 pip install mysqlclient 会提示安装失败(版本不对或者找不到 ...