0

我正在尝试使用 ViewModel 动态填充 WPF 树,但是由于某种原因它不起作用。要么绑定不正确,要么我在代码后面的某个地方搞砸了。

这是我所拥有的样本。在 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>

在代码行为中,我有...

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 方法不完整,我仍然需要一些逻辑来决定是将它们添加到集合中还是添加到集合成员之一的集合中,但是我的所有邮件对象都已添加到集合中但未显示在树视图。

我错过了什么完全明显的事情?当我将项目添加到集合中时,TreeView 不应该自动更新吗?

我想要的是让 TreeView 显示 MessageTree 属性的孩子,以及那些孩子的孩子。

4

3 回答 3

1

在没有看到整个设置的情况下,我并不肯定,但我的猜测是,由于 MessageTree 是一个普通的 CLR 属性(而不是引发 PropertyChanged 或 DependencyProperty 之类的东西,绑定发生在您的MessageTree = new Mail();调用之前。当你设置它时对于一个新实例,绑定系统不会收到通知,因为它是一个普通属性。

另一个潜在的问题是您说代码在代码隐藏中。仅使用该 Binding 语法不会从代码隐藏中获取属性。您可能在代码中的其他地方设置了它,而您没有向我们展示。但通常您不会从视图绑定到代码隐藏,而是绑定到用作视图本身的 DataContext 的 ViewModel。

于 2012-12-28T15:06:05.513 回答
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>

如果这仍然不起作用,我将粘贴我放在一起的整个 LINQPad blob 来测试它。

于 2012-12-28T15:23:36.313 回答
0

不得不给TreeView(树)起个名字,然后

MessageTree = new Mail();

插入

Tree.ItemSource = MessageTree.Children;

我觉得这很难看,但至少现在可以了。感谢大家尝试提供帮助。

于 2012-12-28T17:03:41.067 回答