首页 新闻 会员 周边

WPF MVVMLight:在ViewModel中,开子线程为ObservableCollection集合赋值并且更新UI

0
悬赏园豆:200 [已解决问题] 解决于 2021-02-01 17:00

我在项目中使用TreeView中通过绑定ItemSource为ObservableCollection集合来显示数据。由于构造ObservableCollection的Nodes花费时间比较长,准备使用在子线程中加载,加载完成后把值赋给Nodes。但是报错【 必须在与DependencyObject相同的Thread上创建DependencySource 】

Model代码:

//树视图数据源
    private ObservableCollection<Node> nodes = new ObservableCollection<Node> () ;

    public ObservableCollection<Node> Nodes
    {
        get
        {
            return nodes;
        }
        set { nodes = value; RaisePropertyChanged(() => Nodes); }
    }

填充Nodes代码(临时创建了nodes_c来保存,完成后统一给Nodes,或者完成一个给一个也行):

 public void FillNode()
    {
       try
        {
            nodes_c.Add(integration_.SocialChat(casePath_, Applist_["测试1"], Userlist_));
            nodes_c.Add(integration_.SocialChat(casePath_, Applist_["测试2"], Userlist_));
            nodes_c.Add(integration_.SocialChat(casePath_, Applist_["测试3"], Userlist_));
        }
        catch (Exception ex)
        {
         //log  
        }
    }

调用填充Nodes方法

public CaseViewModel()
    {
        //这里是用MvvmLight的Messenger触发的Load方法
        Messenger.Default.Register<DataHelper>(this, "DB", Load);
    }
    public async void Load(DataHelper x)
    {        
        await Task.Run(FillNode);
        //这里是网上找的方法,但是不起作用。
        //效果等于直接写Nodes = new ObservableCollection<Node>(nodes_c);
        ThreadPool.QueueUserWorkItem(delegate
        {
            System.Threading.SynchronizationContext.SetSynchronizationContext(new
                System.Windows.Threading.DispatcherSynchronizationContext(System.Windows.Application.Current.Dispatcher));
            System.Threading.SynchronizationContext.Current.Post(pl =>
            {
                //把FillNode里临时的nodes_c 赋值给XAML绑定的Nodes属性
                Nodes = new ObservableCollection<Node>(nodes_c);
                LogHelper.log_.Warn(Nodes.Count);
            }, null);
        });

    }
神奇玩偶obbb的主页 神奇玩偶obbb | 初学一级 | 园豆:24
提问于:2021-01-27 03:49
< >
分享
最佳答案
1


Application.Current.Dispatcher.Invoke(action);
代替ThreadPool.QueueUserWorkItem的一堆

这个action就是楼上invoke里面的action

用这个直接就invoke到ui线程执行了

收获园豆:110
猝不及防 | 老鸟四级 |园豆:2781 | 2021-01-27 10:51

我试过了还是不行,依然报错

 public async void Load(DataHelper x)
    {
        await Task.Run(FillNode);
        Application.Current.Dispatcher.Invoke(new Action(() =>
        {
            //把FillNode里临时的nodes_c 赋值给XAML绑定的Nodes属性
            Nodes = new ObservableCollection<Node>(nodes_c);
            LogHelper.log_.Warn(Nodes.Count);
        }));
        Messenger.Default.Unregister<DataHelper>(this, "DB");
    }

报错【System.Windows.Markup.XamlParseException:“必须在与 DependencyObject 相同的线程上创建 DependencySource。”】

神奇玩偶obbb | 园豆:24 (初学一级) | 2021-01-27 15:10

@神奇玩偶obbb: 你的nodes_c是在哪里初始华的,

猝不及防 | 园豆:2781 (老鸟四级) | 2021-01-27 15:51

@神奇玩偶obbb:
试试

 void Load()
        {
            Task.Factory.StartNew(() =>
            {
                //复杂操作
                nodes_c=new xxx();
nodes_c.add(xx);
nodes_c.add(xx);
nodes_c.add(xx);
                Action action = new Action(() =>
                {
                  Node=new xx(){nodes_c};
                    }
                });
                Application.Current.Dispatcher.Invoke(action);
            });
        }

猝不及防 | 园豆:2781 (老鸟四级) | 2021-01-27 15:57

@猝不及防: 刚刚看到。好的 我去 试一下

神奇玩偶obbb | 园豆:24 (初学一级) | 2021-01-28 00:45

@猝不及防:
试过了还是不行。

方法:

public void FillNode()
    {
        Task.Factory.StartNew(() => 
        {

            List<Node> nodes_c = new List<Node>();

            try
            {

                nodes_c.Add(integration_.SocialChat(casePath_, Applist_["t1"], Userlist_));
           
                nodes_c.Add(integration_.Video(casePath_, Applist_["t2"], Userlist_));
          
                nodes_c.Add(integration_.SocialMedia(casePath_, Applist_["t3"], Userlist_));
            }
            catch (Exception ex)
            {                   
            }
            Debug.WriteLine("节点加载完成");

            Action action = new Action(() => {
                Nodes = new ObservableCollection<Node>(nodes_c);
                LogHelper.log_.Warn(Nodes.Count);
            });
            Application.Current.Dispatcher.Invoke(action);
        });

       
    }

调用:

public async void Load(DataHelper x)
    {           
        FillNode();            
        Messenger.Default.Unregister<DataHelper>(this, "DB");
    }
神奇玩偶obbb | 园豆:24 (初学一级) | 2021-01-28 20:23
其他回答(4)
0

因为你在异步方法里面用了ui的代码,所以报这个错

收获园豆:10
不知道风往哪儿吹 | 园豆:2035 (老鸟四级) | 2021-01-27 09:10

对,我就是要异步去加载数据然后展示出来。因为不用异步的加载话,这个流程需要卡死 几十秒。

支持(0) 反对(0) 神奇玩偶obbb | 园豆:24 (初学一级) | 2021-01-28 20:25
0

Invoke(new Action(()=>{
Nodes = new ObservableCollection<Node>(nodes_c);
LogHelper.log_.Warn(Nodes.Count);
}));

收获园豆:40
LiveCoding | 园豆:497 (菜鸟二级) | 2021-01-27 09:38

我试过了还是不行,依然报错

public async void Load(DataHelper x)
{
    await Task.Run(FillNode);
    Application.Current.Dispatcher.Invoke(new Action(() =>
    {
        //把FillNode里临时的nodes_c 赋值给XAML绑定的Nodes属性
        Nodes = new ObservableCollection<Node>(nodes_c);
        LogHelper.log_.Warn(Nodes.Count);
    }));
    Messenger.Default.Unregister<DataHelper>(this, "DB");
}

报错【System.Windows.Markup.XamlParseException:“必须在与 DependencyObject 相同的线程上创建 DependencySource。”】

支持(0) 反对(0) 神奇玩偶obbb | 园豆:24 (初学一级) | 2021-01-27 15:12

@神奇玩偶obbb: 你的nodes_c是在哪里初始化的?

支持(0) 反对(0) 猝不及防 | 园豆:2781 (老鸟四级) | 2021-01-27 15:51

@神奇玩偶obbb: 没搞过WPF,反正你这个是多线程的问题。不难。你再研究研究。

支持(0) 反对(0) LiveCoding | 园豆:497 (菜鸟二级) | 2021-01-27 16:52
0

因为你修改控件的线程不是创建控件的线程。其实你为什么要这样做。你把model绑定到ui,只需修改model,UI自然就变了。你这样做回退到了winform时代了。

收获园豆:40
会长 | 园豆:12401 (专家六级) | 2021-01-27 11:39

我操作的就是TreeView绑定的 集合。但是我Task执行完方法后去操作这个集合的时候。就报那个错

支持(0) 反对(0) 神奇玩偶obbb | 园豆:24 (初学一级) | 2021-01-28 20:26

@神奇玩偶obbb: 如果有空的话写个简单的可运行的demo发来,我调试下试试

支持(0) 反对(0) 会长 | 园豆:12401 (专家六级) | 2021-01-30 10:57
0

谢谢各位大佬给的解决方案。实际是我疏忽了代码。我再XAML代码的模板中绑定了Model中的Brush 类型的Color属性,在异步代码中创建Model实例,并且初始化了Color属性。导致跨线程错误。使用转换器解决。

Converter:

public class ColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (UtilityToolSuite.Tool.SafetyTransformInt32(value) == 0)
        {
            return new SolidColorBrush(Color.FromRgb(0, 0, 0));
        }
        else
        {
            return new SolidColorBrush(Color.FromRgb(255, 0, 0));
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

XAML:

 <TextBlock VerticalAlignment="Center">
  <Run Foreground="{Binding Del,Converter={StaticResource ColorConverter}}" Text="{Binding Del}" />
 </TextBlock>
神奇玩偶obbb | 园豆:24 (初学一级) | 2021-02-01 16:58
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册