2012-12-28 32 views
0

我想通过使用ViewModel动态地填充WPF树,但是,由于某种原因它不工作。要么绑定要么不正确,要么我在后台代码的某个地方搞乱了。WPF TreeView没有被填充

下面是我所拥有的一个样本。 在XAML我定义我的TreeView是这样的...

<TreeView DockPanel.Dock="Left" Width="200" DataContext="{Binding MessageTree}" ItemsSource="{Binding MessageTree}"> 
    <TreeView.ItemContainerStyle> 
      <Style TargetType="{x:Type TreeViewItem}"> 
      <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> 
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
      <Setter Property="FontWeight" Value="Normal" /> 
      <Style.Triggers> 
       <Trigger Property="IsSelected" Value="True"> 
        <Setter Property="FontWeight" Value="Bold" /> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
     </TreeView.ItemContainerStyle> 
     <TreeView.ItemTemplate> 
      <HierarchicalDataTemplate DataType="viewModel:Mail" ItemsSource="{Binding Children}"> 
      <TextBlock Text="{Binding Subject}" /> 
     </HierarchicalDataTemplate> 
    </TreeView.ItemTemplate> 
</TreeView> 

代码Behing我有...

private Mail MessageTree { get; set; } 

而且

using (var mail = new MailParser()) 
{ 
    int count = mail.GetMessageCount(DateTime.Today.AddDays(-10), DateTime.Today.AddDays(1)); 
    MessageTree = new Mail(); 
    for (int i = count - 1; i >= 0; i--) 
    { 
     MailMessage msg = mail.RetrieveMessage(i); 
     if (msg != null) 
     { 
      MessageTree.Add(msg); 
     } 
     if (backgroundWorker != null) 
     { 
      decimal perc = (100.0m - (((i + 1.0m)*100.0m)/count)); 
      backgroundWorker.ReportProgress((int) perc, "Recebendo mensagens... " + perc.ToString("N2") + "%"); 
      if (backgroundWorker.CancellationPending) 
      { 
       e.Cancel = true; 
       break; 
      } 
     } 
    } 
} 

邮件被定义为

public sealed class Mail : INotifyPropertyChanged 
{ 
    private readonly ObservableCollection<Mail> _children; 
    private readonly MailMessage _msg; 
    private readonly Mail _parent; 

    private bool _isExpanded; 
    private bool _isSelected; 

    public Mail() 
    { 
     _msg = new MailMessage {Subject = "Empty"}; 
     _parent = null; 
     _children = new ObservableCollection<Mail>(); 
    } 

    public Mail(MailMessage msg, Mail parent = null) 
    { 
     _msg = msg; 
     _parent = parent; 
     _children = new ObservableCollection<Mail>(); 
    } 

    public IEnumerable<Mail> Children 
    { 
     get { return _children; } 
    } 

    public string Subject 
    { 
     get { return _msg.Subject; } 
    } 

    public bool IsExpanded 
    { 
     get { return _isExpanded; } 
     set 
     { 
      if (value != _isExpanded) 
      { 
       _isExpanded = value; 
       OnPropertyChanged(); 
      } 
      if (_isExpanded && _parent != null) 
       _parent.IsExpanded = true; 
     } 
    } 

    public bool IsSelected 
    { 
     get { return _isSelected; } 
     set 
     { 
      if (value != _isSelected) 
      { 
       _isSelected = value; 
       OnPropertyChanged(); 
      } 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    public void Add(MailMessage msg) 
    { 
     _children.Add(new Mail(msg, this)); 
     OnPropertyChanged("Children"); 
    } 

    private void OnPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 
    } 
} 

我找不到任何东西与网上发现的例子截然不同,它不起作用。 Add方法是不完整的,我仍然需要一些逻辑来决定是将它们添加到集合还是集合成员的集合中,但正如我所有的Mail对象都添加到集合中但没有显示在集合中树视图。

我错过了什么完全明显的东西?当我向物品添加物品时,TreeView是否应该自动更新?

我想要的是为TreeView显示MessageTree属性的子项以及这些子项的子项。

回答

1

编辑:无法看到我的手机上的整个事情 - 修正答案基于实际看到的一切的能力。 :)

MOREEDIT:根据评论更新,让我们从头开始!我们先让它成为'INotifyPropertyChange ...下一步,让我们让“MessageTree”成为一个邮件集合,而不仅仅是一个邮件集合(它是'会作出有约束力的语义更容易,相信我)

public class WhateverContainsTheTree : Window, INotifyPropertyChanged 
{ 
    public WhateverContainsTheTree() 
    { 
     this.Loaded += OnLoaded; 
     this._messageTree = new ObservableCollection<Mail>(); 
     this.DataContext = this; 
    } 

    private void OnLoaded(object sender, RoutedEventArgs e) 
    { 
     _worker = new BackgroundWorker(); 
     _worker.DoWork += WorkerWorkin; 
     _worker.RunWorkerAsync(); 
    } 

    private BackgroundWorker _worker; 
    private ObservableCollection<Mail> _messageTree;  
    public ObservableCollection<Mail> MessageTree 
    { 
     get { return _messageTree; } 
     set { _messageTree = value; RaisePropertyChanged("MessageTree"); } 
    } 

    public event PropertyChangedEventHandler PropertyChanged = delegate {}; 
    private void RaisePropertyChanged(string propertyName) 
    { 
     PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    private void WorkerWorkin(object sender, DoWorkEventArgs e) 
    { 
       // obviously, change this to your stuff; I added a ctor so I could pass a string 
     Thread.Sleep(3000); 
     Console.WriteLine("Ok, setting message tree"); 
     Dispatcher.Invoke(
      System.Windows.Threading.DispatcherPriority.Normal, 
      (Action)(() => 
      { 
       var mail1 = new Mail("Mail#1:Changed from background thread"); 
       var mail2 = new Mail("Mail#2:Submail of mail #1"); 
       var mail3 = new Mail("Mail#3:Submail of mail #2"); 
       var mail4 = new Mail("Mail#4:Submail of mail #1"); 
       var mail5 = new Mail("Mail#5:Submail of mail #4"); 
       mail1.Children.Add(mail2); 
       mail1.Children.Add(mail4); 
       mail2.Children.Add(mail3); 
       mail4.Children.Add(mail5); 
       MessageTree.Add(mail1); 
      }) 
     ); 
    } 
} 

另外,就像我在原来的响应会说,让我们稍微调整Mail.Children:

public ObservableCollection<Mail> Children 
{ 
    get { return _children; } 
} 

和这里的什么我用树视图xaml:

<TreeView DockPanel.Dock="Left" Width="200" ItemsSource="{{Binding MessageTree}}"> 
    <TreeView.ItemContainerStyle> 
      <Style TargetType="{{x:Type TreeViewItem}}"> 
      <Setter Property="IsExpanded" Value="{{Binding IsExpanded, Mode=TwoWay}}" /> 
      <Setter Property="IsSelected" Value="{{Binding IsSelected, Mode=TwoWay}}" /> 
      <Setter Property="FontWeight" Value="Normal" /> 
      <Style.Triggers> 
       <Trigger Property="IsSelected" Value="True"> 
        <Setter Property="FontWeight" Value="Bold" /> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
     </TreeView.ItemContainerStyle> 
     <TreeView.ItemTemplate> 
      <HierarchicalDataTemplate DataType="viewModel:Mail" ItemsSource="{{Binding Children}}"> 
       <TextBlock Text="{{Binding Subject}}" /> 
      </HierarchicalDataTemplate> 
    </TreeView.ItemTemplate> 
</TreeView> 

如果这个STILL不起作用,我会粘贴整个LINQPad blob来放在一起来测试它。

+0

不起作用 - 对不起 – 537mfb

+0

不应该datacontext是什么持有的项目吗?在这种情况下MessageTree? – 537mfb

+0

啊哈 - 这就是我在电话里看到这个的时候...部分被切断了。让我修改我的答案... – JerKimball

1

没有看到整个设置,我不积极,但我的猜测是,因为MessageTree是一个普通的CLR属性(而不是引发PropertyChanged或DependencyProperty或什么的东西,该绑定发生在您拨打MessageTree = new Mail();之前。当你将它设置为一个新的实例时,由于绑定系统是一个普通属性,因此它没有得到通知。

另一个潜在的问题是,你说代码在代码隐藏中,只是使用绑定语法不会从代码隐藏中挑选一个属性,你可能会在代码中的其他位置设置它,但是一般情况下,你不会从View中绑定到代码隐藏,你会绑定到一个ViewModel被用作DataContext为视图本身。

+0

MessageTree是类型邮件至极是我的ViewModel并且实现INotifyPropertyChanged - 如果这就是你的意思。 – 537mfb

+0

至于新的邮件()线,这是一个好点 - 我会在哪里初始化它呢?我尝试设置base.DataContext到MessageTree后,我呼吁新的权利,但没有奏效 – 537mfb

+0

好吧给了你+1因为你带领我一半的答案 - 加入DataContext = MessageTree.Children;使我必须将MessageTree.Add行放在调度程序调用中,因为绑定 - 仍然没有在TreeView中显示任何内容,尽管 – 537mfb

0

只好给一个名字到TreeView(树),然后

MessageTree = new Mail(); 

插入

Tree.ItemSource = MessageTree.Children; 

后,我觉得这是丑陋,但至少现在的工作。谢谢大家的帮助。

+0

这真的很奇怪......你究竟在哪里做MessageTree = new Mail()调用?我看到使用...块,但实际发生在哪里? – JerKimball

+0

在backgroundworker中 - backgroundworker是window_loaded事件的启动者,用于使用来自gmail帐户的邮件填充ViewModel – 537mfb

+0

Aha!这解释了一些事情......让我再次重新修改我的答案... – JerKimball