【问题标题】:C# ListView doesn't updateC# ListView 不更新
【发布时间】:2011-12-30 06:58:42
【问题描述】:

我认为 C# win 表单在某些情况下不能很好地重绘的问题在不同的地方都有介绍,但是,我没有设法通过使用我在网上找到的简单 sn-ps 来解决我的问题。

我的问题:在一个表单上,我有一个 listView,我将它关联到一个自定义数据持有者(2 列、一个键和一个最后更新日期)。从不同的地方,我需要调用 updateTime(key) 方法,然后复制 GUI 中的更改。模型会改变,但我的 listView 永远不会。

我有一个包含 ListView 的表单,看起来像这样:

partial class VolsPane : UserControl, IGUIPane 
{
   private ListView listView1;
   private ListModel listModel1;     //ListModel is 100% homemade
   ...
   public VolsPane()
   {
       ...
       listModel1.setList(listView1);
   }
}

为我的 listView 保存数据的类是这样的:

class ListModel
{
    private Dictionary<string, DateTime> Underlying;
    private ListView list;

    ...

    public ListModel(string nexusKey)
    {
        ...
    }

    ...

    public void setList(ListView list)
    {
        this.list = list;
    }


    public void updateTime(string ric)
    {
        Underlying[ric] = DateTime.UtcNow;
        updateView();
    }

    public void updateView()
    {
        this.list.Clear();
        this.list.Items.AddRange(this.underlyingToListItems());
    }

    ...

    public ListViewItem[] underlyingToListItems()
    {
        ListViewItem[] res = new ListViewItem[Underlying.Keys.Count];
        int i = 0;
        foreach (string ric in Underlying.Keys)
        {
            res[i] = new ListViewItem(new string[] { ric, Underlying[ric].ToString("MMM-dd hh:mm:ss") });
            i++;
        }
        return res;
    }

}

我确实意识到问题出在我的 updateView() 中。在调试中,代码肯定会去那里。相信问题将通过异步“调用”来解决,我参考了这篇似乎是参考的帖子:Stack overflow : Automating the invoke...

然后试试这个:

    private void updateView()
    {
        if (this.list.InvokeRequired)
        {
            this.list.Invoke(new MethodInvoker(() => { updateView(); }));
        }
        else
        {
            this.list.Items.Clear();
            //this.list.Clear();
            this.list.Items.AddRange(this.underlyingToListItems());
        }
    }

它构建但没有效果。在调试模式下,永远不会进入 'if' 分支,始终是 'else'。

那么这个:

    private void updateView()
    {
        this.list.Invoke((MethodInvoker)delegate
        {
            this.list.Items.Clear();
            //this.list.Clear();
            this.list.Items.AddRange(this.underlyingToListItems());
        });
    }

我收到“InvalidOperationException:在创建窗口句柄之前无法在控件上调用 Invoke 或 BeginInvoke。”

我必须在这里遗漏什么明显的东西?还是我的问题实际上不是我想的?

谢谢大家!

【问题讨论】:

    标签: c# winforms multithreading user-interface invoke


    【解决方案1】:

    你是对的,问题在于 updateView() 代码。您确实需要调用 UI 线程,但问题是尚未为控件创建句柄。使用 WinForms 时遇到的一个问题是,如果尚未创建句柄,则 InvokeRequired 实际上将返回 false。请参阅MSDN documentation 的说明:

    如果控件的句柄尚不存在,InvokeRequired 将向上搜索控件的父链,直到找到具有窗口句柄的控件或窗体。如果找不到合适的句柄,InvokeRequired 方法返回 false

    这就是您对 InvokeRequired 的检查总是失败的原因。我已经看到通过几种方式解决了这个问题。一种解决方案是将回调附加到控件的句柄创建事件:

    public class HandleHookedListView: ListView
    {
        private EventHandler _handleCreatedEvent;
    
        public HandleHookedListView(): base()
        {
            _handleCreatedEvent = new EventHandler(HandleHookedControl_HandleCreated);
            this.HandleCreated += _handleCreatedEvent;
        }
    
        private bool _handleIsCreated;
    
        public bool HandleIsCreated
        {
            get { return _handleIsCreated; }
            set { _handleIsCreated = value; }
        }
    
        void HandleHookedControl_HandleCreated(object sender, EventArgs e)
        {
            Debug.Print("Handle Created");
            this.HandleIsCreated = true;
    
            // Unhook the delegate
            if (_handleCreatedEvent != null)
                this.HandleCreated -= _handleCreatedEvent;
        }
    }
    

    然后您必须修改您的 updateView 以检查句柄是否已创建。在这种情况下,您的 ListView (list) 实例已替换为新的 HandleHookedListView

    private void updateView()
    {
        var handleCreated = this.list.HandleIsCreated;
        if (this.list.InvokeRequired && handleCreated)
        {
            // Handle is created and invoke is required.
            this.list.Invoke(new MethodInvoker(() => { updateView(); }));
        }
        else if (handleCreated)
        {
            // In this case your control's handle has been created and invoke really 
            // isn't required go ahead and do the update
    
            this.list.Items.Clear();
            this.list.Items.AddRange(this.underlyingToListItems());
        }
        else
        {
            // You cannot update yet.  The handle has not been created.  Depending on if
            // you need to "queue" these updates you can either collect them or just 
            // ignore them if a subsequent call to updateView() after the handle has been
            // created will be sufficient
        }
    }
    

    这里真正的关键是您试图在控件完全初始化之前对其进行更新。

    【讨论】:

      【解决方案2】:

      首先

      没有构建,我的列表模型不存在调用。

      据我所知,Invoke 是 Control 类的方法。 因此,如果没有从 Control 继承的任何类实例,就不能在 ListModel 类中调用它。 使用

      this.list.Invoke( 
      

      在调试模式下,永远不会进入“if”分支,总是进入“else”。

      这可能意味着 this.list.InvokeRequired 在 GUI thead 中被调用。

      这也可能意味着 list.InvokeRequired 在 this.list 至少被绘制一次之前被调用过。 这是一个棘手的时刻。如果 Control 类的实例尚未绘制,则 gdi+(或 C# WinForm 绘制的底层内容)尚未初始化。所以没有什么要同步的。 请仔细检查。

      【讨论】:

      • 进行了更改,但仍然出现异常
      • @jerome G:再次:“InvalidOperationException:在创建窗口句柄之前,无法在控件上调用 Invoke 或 BeginInvoke。”这意味着尚未创建控件。我不能提出任何建议这是问题的根源。
      • 好吧,事情是列表控件和表单在发生这种情况时就位。我可以对其进行各种其他操作。只是没有这个。你能详细说明一下吗? Exception里的英文我也能看懂...
      • @jerome G:尝试一些愚蠢的事情,例如添加 listView1.Update();就在 listView1 = new ListView(); 之后如果生成代码中的那一行,您可以将其临时移动到构造函数中。
      • 没有任何意义,但我试过了,没有效果。我不认为绘画真的与这个问题有关,或者至少它会是一个不同的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-24
      • 2020-09-10
      • 1970-01-01
      • 2015-07-08
      • 1970-01-01
      • 2015-12-29
      • 1970-01-01
      相关资源
      最近更新 更多