WPF 仿百度云的拖拽到系统的下载

大概是两年前做的一个项目,跟百度云盘的拖拽下载功能基本差不多。最近有人问我,我看了一下以前的代码,感觉应该写点什么记录下来,方便以后可以直接使用。记得当时网上查了好久基本没有,问了好多人,答案大多都好像无法实现。公司当时WPF就我一个人,最后没办法,只有观察琢磨百度云的拖拽下载,观察了一周左右,最后终于自己弄出来了。

说下思路:(注意将拖拽拆分为复制和粘贴,拖起为复制,到达系统中鼠标弹起为粘贴)

1.用户在软件界面拖拽了文件或者点击了复制,如果这时候用户在系统中点击了粘贴或者文件拖拽的到系统中,我们是无法捕捉这个操作的,所以这时候我们想办法找到a.用户什么时候在系统中点击了粘贴,或者拖拽到系统中鼠标弹起了(相当于在系统中点击了粘贴);b.用户在系统中哪个文件夹下,点击了粘贴或者拖拽的鼠标弹起了,即最终的我们要下载的目录。
2.至于如何判断用户的点击粘贴动作或者拖拽的鼠标弹起操作,我们需要借助系统剪切板完成。原因:系统的文件拖拽实际也是复制粘贴过程,我们是否可以用一个中间文件,我们在软件中拖起文件时,给系统一个中间文件,到系统剪切板,后面我称这个文件为标记文件,当标记被粘贴了或者被拖拽到某一文件夹,那不就是我们需要的下载目录和最终的下载时间?
3.具体实现:用户在软件中点击复制,或者拖拽了文件(鼠标没有弹起,仅仅是复制,没有粘贴),这时候,我们需要在系统的剪切板中添加一个文件(标记文件),文件路径随意,不要改动,但是一定要隐蔽,而且不要更换,一般是C盘的AppData\Local\Temp\我们的应用程序文件夹,专业一点的都懂的,这个文件夹是干嘛的。将这个文件放入系统的剪切板,剪切板有个类型选择,选剪切,不要选复制,原因:当用户在系统中点击粘贴或者拖拽的鼠标弹起时,该文件会被切剪到用户指定的文件夹中,这个文件夹就是我们需要的文件夹路径,这个事件发生的时候,就是我们需要干活的时候,我们需要干的,最重要的一步,就是监听这个C盘的AppData\Local\Temp\我们的应用程序文件夹,查看中间文件状态(是否被剪切)。
4.最后一步:监听。获取当前系统中被激活的窗口,如果当前被激活的是文件夹,那么就监听,至于怎么获取被激活的窗体,网上有,要用win32函数,我就不多说了,后续代码也可以参考。
5.被监听的文件夹中如果出现那个标记文件被剪切走(删除了),我们就发起下载过程,同时删掉标记文件,这个下载的目的地址,就是标记文件被剪切到达的文件夹。
思路证明:把百度云的文件,随意直接往qq好友面板中拖拽,会有类似的一个标记文件存在;或者下载时观察AppData\Local\baidu这个文件夹,也会有这样一个中间文件作为标记文件,在下载的目录中也存在这样一个文件。

好了,思路是思路,最终还是要验证,最简单的验证,上代码:
1.先定义一个文件类,这个没啥好纠结的
  class FileName
    {
        private string _Name;
        public string Name
        {
            get { return _Name; }
            set { _Name = value; }
        }

        public FileName(string name)
        {
            this.Name = name;
        }
    }

2.定义一个窗口类,也没啥好多说的
 <Grid>
        <ListView Name="listView" ItemsSource="{Binding}" MouseMove="listView_MouseMove" >
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="文件名" Width="320">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <Label Content="{Binding Name,Mode=OneTime}" Visibility="{Binding NameVisible}"/>
                                </StackPanel>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
3.添加监听类,重点来了
  class WatchPasteLogManager
    {
        private static FileSystemWatcher watcherPasteLog;
        private static string PastePath = "";

        /// <summary>
        /// 开始监听临时路径下的标记文件
        /// </summary>
        /// <param name="path"></param>
        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        public static void WatchPasteLog(string path)
        {
            if (null == path)
            {
                return;
            }
            watcherPasteLog = new FileSystemWatcher();
            watcherPasteLog.Path = path;
            watcherPasteLog.EnableRaisingEvents = true;
            watcherPasteLog.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
               | NotifyFilters.FileName | NotifyFilters.DirectoryName;
            watcherPasteLog.Filter = "Test.temp";
            watcherPasteLog.Deleted += new FileSystemEventHandler(OnChanged);
        }

        /// <summary>
        /// 监听事件触发方法
        /// </summary>
        private static void OnChanged(object source, FileSystemEventArgs e)
        {
            if (e.ChangeType == WatcherChangeTypes.Deleted)
            {
                string dstPath = GetForegroundWindowPath() + "Test.temp";
                if (File.Exists(dstPath))
                {
                    //这个路径就是我们要找的用户粘贴的路径,这个方法被触发的时间,就是我们要的下载时间
                    PastePath = GetForegroundWindowPath();
                    //下载
                    System.Net.WebClient client = new System.Net.WebClient();

                    //这个地址可以替换,看具体需要,这里我找不到好的链接,直接用qq的下载链接了
                    client.DownloadFile("http://dldir1.qq.com/qqfile/qq/QQ6.0/11743/QQ6.0.exe", PastePath + "\\qq.exe");

                    watcherPasteLog.EnableRaisingEvents = false;
                }
            }
        }

        private static string ForegroundWindowPath;
        public static string GetForegroundWindowPath()
        {
            EnumWindows(Report, 0);
            return ForegroundWindowPath;
        }

        #region 获取当前激活的窗口路径
        public delegate bool CallBack(int hwnd, int y);

        [DllImport("user32.dll")]
        public static extern int EnumWindows(CallBack x, int y);
        [DllImport("user32.dll")]
        public static extern int GetWindowText(int hwnd, StringBuilder lptrString, int nMaxCount);
        [DllImport("user32.dll")]
        public static extern int GetParent(int hwnd);
        [DllImport("user32.dll")]
        public static extern bool IsWindowVisible(int hwnd);
        [DllImport("user32.Dll ")]
        public static extern void GetClassName(IntPtr hwnd, StringBuilder s, int nMaxCount);
        [DllImport("user32.dll ")]
        public static extern IntPtr FindWindowEx(IntPtr parent, IntPtr childe, string strclass, string FrmText);
        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern IntPtr GetForegroundWindow();
        [DllImport("user32.dll")]
        public static extern bool SetForegroundWindow(int hWnd);
        [DllImport("user32.dll")]
        public static extern IntPtr GetActiveWindow();

        private static string GetFormClassName(IntPtr ptr)
        {
            StringBuilder nameBiulder = new StringBuilder(255);
            GetClassName(ptr, nameBiulder, 255);
            return nameBiulder.ToString();
        }

        private static string GetFormTitle(IntPtr ptr)
        {
            StringBuilder titleBiulder = new StringBuilder(255);
            GetWindowText((int)ptr, titleBiulder, 255);
            return titleBiulder.ToString();
        }

        public static bool Report(int hwnd, int lParam)
        {
            int pHwnds = (int)GetForegroundWindow();
            int pHwnd = (int)GetActiveWindow();
            if (pHwnd == 0 && hwnd == pHwnds && IsWindowVisible(hwnd) == true)
            {
                IntPtr cabinetWClassIntPtr = new IntPtr(hwnd);
                string cabinetWClassName = GetFormClassName(cabinetWClassIntPtr);
                string cabinetWClassTitle = GetFormTitle(cabinetWClassIntPtr);
                if (cabinetWClassName.Equals("CabinetWClass", StringComparison.OrdinalIgnoreCase))
                {
                    IntPtr workerWIntPtr = FindWindowEx(cabinetWClassIntPtr, IntPtr.Zero, "WorkerW", null);
                    IntPtr reBarWindow32IntPtr = FindWindowEx(workerWIntPtr, IntPtr.Zero, "ReBarWindow32", null);
                    IntPtr addressBandRootIntPtr = FindWindowEx(reBarWindow32IntPtr, IntPtr.Zero, "Address Band Root", null);
                    IntPtr msctls_progress32IntPtr = FindWindowEx(addressBandRootIntPtr, IntPtr.Zero, "msctls_progress32", null);
                    IntPtr breadcrumbParentIntPtr = FindWindowEx(msctls_progress32IntPtr, IntPtr.Zero, "Breadcrumb Parent", null);
                    IntPtr toolbarWindow32IntPtr = FindWindowEx(breadcrumbParentIntPtr, IntPtr.Zero, "ToolbarWindow32", null);

                    string title = GetFormTitle(toolbarWindow32IntPtr);
                    int index = title.IndexOf(':');
                    index++;
                    string path = title.Substring(index, title.Length - index);
                    if (Directory.Exists(path))
                    {
                        path = path + "\\";
                        if (path.StartsWith(" "))
                        {
                            path = path.Substring(1, path.Length - 1);
                        }
                        ForegroundWindowPath = path;
                    }
                }
                else if (cabinetWClassName.Equals("Progman", StringComparison.OrdinalIgnoreCase) && cabinetWClassTitle.Equals("Program Manager", StringComparison.OrdinalIgnoreCase))
                {
                    ForegroundWindowPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\";
                }
            }
            return true;
        }
        #endregion
    }
4.添加具体的拖拽逻辑
 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            List<FileName> list = new List<FileName>();
            list.Add(new FileName("1111"));
            list.Add(new FileName("2222"));
            list.Add(new FileName("3333"));
            list.Add(new FileName("4444"));
            listView.ItemsSource = list;
        }

        private void listView_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                //FileListItem item = (FileListItem)listView.SelectedItem;
                if (!System.IO.Directory.Exists(System.IO.Path.GetTempPath() + "temp\\" + "\\"))
                {
                    System.IO.Directory.CreateDirectory(System.IO.Path.GetTempPath() + "temp\\" + "\\");
                }
                string localPasteLog = System.IO.Path.GetTempPath() + "temp\\" + "\\" + "Test.temp";
                CreateFiles(localPasteLog, DateTime.Now.ToString());
                string[] files = new string[1];
                files[0] = System.IO.Path.GetTempPath() + "temp\\" + "\\" + "Test.temp";
                //@这一步是将我们的标记文件给系统,拖拽鼠标弹起时,系统会将这个文件剪切到指定目录,注意一定要是DragDropEffects.Move,即剪切模式
                DragDrop.DoDragDrop(listView, new DataObject(DataFormats.FileDrop, files), DragDropEffects.Move /* | DragDropEffects.Link */);
                //监听temp目录下的标记文件,如果被剪切走(删除),就是发生下载的时机
                WatchPasteLogManager.WatchPasteLog(localPasteLog.Replace("Test.temp", ""));
            }
        }

        public void CreateFiles(string path, string context)
        {
            try
            {
                StreamWriter f = new StreamWriter(path, true);
                f.WriteLine(context);
                f.Close();
            }
            catch (Exception ex)
            {
                return;
            }
        }
    }


完成上面的,拖拽一下,就可以下载一个qq.exe,将qq.exe换成点击的那个ListViewItem代表的路径,这个对能点进来看博客的大爷来说简直就是小儿科了。用户的点击复制,在系统中去粘贴也是一样的操作,不过要修改一下剪切板,没有DragDrop.DoDragDrop可以直接使用。
这里附上剪切板,不具体说明了(copytoClipboard方法取代上面@标记处)
 class FileSetToClipboard
    {
        public static void CopyToClipboard(string[] files, bool cut)
        {
            if (files == null) return;
            IDataObject data = new DataObject(DataFormats.FileDrop, files);
            MemoryStream memo = new MemoryStream(4);
            byte[] bytes = new byte[] { (byte)(cut ? 2 : 5), 0, 0, 0 };
            memo.Write(bytes, 0, bytes.Length);
            data.SetData("Preferred DropEffect", memo);
            Clipboard.SetDataObject(data, false);  //false程序退出时不保存在剪切板
        }
    }
文采不怎么样,而且有bug和异常没捕捉,但是思路大致已经说清了,基本意思已经到了,大家将就着看吧。

总结:程序设计,你看到的,不一定是真的。很多时候我们无法直接获取到的东西,其实都可以用不同的方法去解决。只要有人做到了,说明必定有方法可以解决,只是你想没有想到!也就是没有做不到,只有想不到!

猜你喜欢

转载自blog.csdn.net/zq1564171310/article/details/72770507
WPF