首页 > 解决方案 > 无法在 CSharp 中使用 Win32 消息填充 TreeView 控件

问题描述

我正在与TreeView ControlinWindows Forms合作C#

由于实际人口部分TreeView Control需要大量时间,因此会冻结 UI。因此,我尝试使用PostMessage Win32 API后台线程进行填充,但我发现Treeview没有插入 Items。

所以我将代码从后台线程移动到主线程。但是插入项目代码也不起作用。我得到了类似的代码TreeView,并尝试使用互操作例程C++来做同样的事情。C#

我不会采用通常的C#方式,treeView1.Nodes.Add("...")因为即使我遵循从另一个线程填充 UI 控件的Delegate方法和方法,它也会冻结 UI。BackgroundWorker我在下面给出我使用的代码。有人可以帮助找到代码的问题。

另请注意,对于TreeView控件,我使用的是从类派生的自己的简单类TreeView,在该类中我重写了WndProc验证 Windows 消息流的方法,并且我可以看到消息(TVM_INSERTITEM)实际上正在通过,但该项目仍然没有得到人口稠密

此外,我从用于控制的后台线程中获得了类似的互操作代码,ListView但我的尝试TreeView到目前为止还没有成功。

表单类代码

using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace UpdateTreeViewFromAnotherThread
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TVITEM
{
    public uint mask;
    public IntPtr hItem;
    public uint state;
    public uint stateMask;
    public IntPtr pszText;
    public int cchTextMax;
    public int iImage;
    public int iSelectedImage;
    public int cChildren;
    public uint lParam;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct TVINSERTSTRUCT
{
    public IntPtr hParent;
    public IntPtr hInsertAfter;
    public TVITEM item;
}

public enum TreeViewInsert
{
    TVI_ROOT = -0x10000,
}

[Flags]
public enum TreeViewItemMask
{
    TVIF_TEXT = 0x0001,
}


public partial class Form1 : Form
{
    const int TV_FIRST = 0x1100;
    IntPtr tvInsItemPtr;
    TVINSERTSTRUCT tvins;
    IntPtr handleTreeView;

    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
    public enum TreeViewMessage
    {
        TVM_INSERTITEM = TV_FIRST + 50,        
    }
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        handleTreeView = treeView1.Handle;
        //treeView1.Nodes.Add("hello");
        PopulateTree(handleTreeView);
    }

    public void PopulateTree(IntPtr handle)
    {
        tvins = new TVINSERTSTRUCT();
        tvins.item.mask = (uint)TreeViewItemMask.TVIF_TEXT;

        // Set the text of the item. 
        string productName = "Product";
        string value = productName;
        byte[] buffer = new byte[100];
        buffer = Encoding.Unicode.GetBytes(value + "\0");
        tvins.item.pszText = Marshal.AllocHGlobal(buffer.Length);
        Marshal.Copy(buffer, 0, tvins.item.pszText, buffer.Length);

        tvins.hParent = IntPtr.Zero;
        tvins.hInsertAfter = (IntPtr)(TreeViewInsert.TVI_ROOT);
        tvInsItemPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tvins));
        Marshal.StructureToPtr(tvins, tvInsItemPtr, true);
        PostMessage(treeView1.Handle, (uint)TreeViewMessage.TVM_INSERTITEM, IntPtr.Zero, tvInsItemPtr);
        //SendMessage(treeView1.Handle, (int)TreeViewMessage.TVM_INSERTITEM, 0, tvInsItemPtr);
    }
}
}

MyTreeView 类代码

using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace UpdateTreeViewFromAnotherThread
{
    class MyTreeView:TreeView
    {
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x1132)
            {
                TVINSERTSTRUCT anotherTVInsertStruct;
                anotherTVInsertStruct = (TVINSERTSTRUCT)Marshal.PtrToStructure(m.LParam, typeof(TVINSERTSTRUCT));
                string anotherNodeText = Marshal.PtrToStringAnsi(anotherTVInsertStruct.item.pszText);
            }
            if(m.Msg == 0x113F)
            {
                TVITEM anotherTVItem;
                anotherTVItem = (TVITEM)Marshal.PtrToStructure(m.LParam, typeof(TVITEM));
                string anotherNodeText = Marshal.PtrToStringAnsi(anotherTVItem.pszText);
            }
            base.WndProc(ref m);

            //Trace.WriteLine(m.Msg.ToString() + ", " + m.ToString());
        }
    }
}

更新_1

使用以下代码阻止了 Treeview 的 NM_CUSTOMDRAW。感谢链接处的代码。

        protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_REFLECT + WM_NOTIFY:
                {
                    NMHDR nmhdr = (NMHDR)m.GetLParam(typeof(NMHDR));
                    switch ((int)nmhdr.code)
                    {
                        case NM_CUSTOMDRAW:
                            NMTVCUSTOMDRAW nmTvDraw = (NMTVCUSTOMDRAW)m.GetLParam(typeof(NMTVCUSTOMDRAW));
                            switch (nmTvDraw.nmcd.dwDrawStage)
                            {
                                case CDDS_ITEMPREPAINT:
                                    m.Result = (IntPtr)CDRF_DODEFAULT;
                                    break;
                            }
                            Marshal.StructureToPtr(nmTvDraw, m.LParam, false);
                            return;
                    }
                    break;
                }
        }
        base.WndProc(ref m);
    }

所以现在如果我更改我之前的 PopulateTree 函数(注意 Thread.Sleep())并将其调用到后台线程,如下所示,它不会在填充过程中冻结 UI

    private void button1_Click(object sender, EventArgs e)
    {
        handleTreeView = treeView1.Handle;
        Thread backgroundThread = new Thread(() => PopulateTree(handleTreeView));
        backgroundThread.Start();
    }

    public void PopulateTree(IntPtr handle)
    {
        for(int i =0; i< 1000; i++)
        { 
            tvins = new TVINSERTSTRUCT();
            tvins.item.mask = (uint)TreeViewItemMask.TVIF_TEXT;

            // Set the text of the item. 
            string productName = "Product_" + i.ToString();
            string value = productName;
            byte[] buffer = new byte[100];
            buffer = Encoding.Unicode.GetBytes(value + "\0");
            tvins.item.pszText = Marshal.AllocHGlobal(buffer.Length);
            Marshal.Copy(buffer, 0, tvins.item.pszText, buffer.Length);

            tvins.hParent = IntPtr.Zero;
            tvins.hInsertAfter = (IntPtr)(TreeViewInsert.TVI_ROOT);
            tvInsItemPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tvins));
            Marshal.StructureToPtr(tvins, tvInsItemPtr, true);
            PostMessage(handle, (uint)TreeViewMessage.TVM_INSERTITEM, IntPtr.Zero, tvInsItemPtr);
            Thread.Sleep(1000);
        }
    }

标签: c#winforms

解决方案


感谢 Jimi 和 MikiD,我能够使用 BeginUpdate 和 BeginInvoke 方法产生相同的非冻结 UI 行为。我改变了我的代码如下

        private async void button1_Click(object sender, EventArgs e)
    {
        await Task.Run(() => PopulateTree());
    }

    private async void PopulateTree()
    {
        for(int i = 0;i< 1000;i++)
        {
            treeView1.BeginInvoke( (MethodInvoker)delegate ()
                {
                    treeView1.BeginUpdate();
                    treeView1.Nodes.Add("Product_" + i.ToString());
                    treeView1.EndUpdate();
                }
            );

            System.Threading.Thread.Sleep(1000);
        }
    }

推荐阅读