视图中的视图未更新 - Caliburn.Micro

3
我遇到了一个问题,当将数据网格附加到视图中时,它没有反映其集合的更改。 更准确地说,我在MainView中有一个SecondView。 在SecondView中,我有一个数据网格,其中autogeneratecolumns设置为true; 当数据网格首次呈现时,它显示适当的列和标题。 但是,当我填充附加到它的列表时,没有反映任何更改。
以下是两个视图及其各自的视图模型的完整代码: MainWindowView:
<Window x:Class="MyApp.MainWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:cal="http://www.caliburnproject.org"
    xmlns:views="clr-namespace:MyApp"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindowView" Height="300" Width="300">
<Grid>
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="File">
                <MenuItem Header="Open" x:Name="Open"/>
                <MenuItem Header="Exit" x:Name="Exit"/>
            </MenuItem>
        </Menu>
        <StackPanel DockPanel.Dock="Bottom">
            <views:SecondView/>
        </StackPanel>
    </DockPanel>
</Grid>

MainWindowViewModel:

namespace MyApp
{
[Export(typeof(IShell))]
internal class MainWindowViewModel : Screen, IShell
{
    Regex expression = new Regex(@"^N\d\.C\d\.D\d\.R\d:\s\s\s-\d"); //ex. "N1.C1.D2.R1:   -3"        
    SecondViewModel svm = new SecondViewModel();        
    public void Open()
    {
        Microsoft.Win32.OpenFileDialog openFile = new Microsoft.Win32.OpenFileDialog();
        openFile.Multiselect = true;
        openFile.Filter = "Text Files(*.txt)|*.txt|Log Files(*.log)|*.log|All Files(*.*)|*.*";
        openFile.Title = "Open File(s)";
        bool? userClickedOK = openFile.ShowDialog();
        string[] _fileNames = openFile.FileNames;
        if (userClickedOK == true)
        {
            if (_fileNames != null)
            {
                for (int i = 0; i < _fileNames.Length; i++)
                {
                    ValidFiles(_fileNames[i]);
                }
            }
        }
    }
    public void Exit()
    {
        App.Current.Shutdown();
    }
    /* ValidFiles() accepts a string containing a filename and creates a Streamreader that reads the file if it is not a Boxboro file.
     */
    public void ValidFiles(string filename)
    {
        string line;
        using (StreamReader sr = new StreamReader(filename))
        {
            while ((line = sr.ReadLine()) != null)
            {
                if (line.Contains("Mono Status"))
                {
                    Console.WriteLine("File(s) not supported by this parser. Please select a valid file.");
                    break;
                }
                else
                {
                    IsMatch(line);
                }
            }
        }
    }
    /* IsMatch() accepts a string "input" and determines which parsing method to send the string to, if any.
     * Strings not matching any of the initial criteria are not processed to limit overhead.
     */
    public void IsMatch(string input)
    {
        Match match = expression.Match(input);
        if (match.Success)
        {
            svm.GetData(input);
        }
    }
}

}

SecondWindowView:

<UserControl x:Class="MyApp.SecondView"
         xmlns:cal="http://www.caliburnproject.org"
         cal:Bind.Model="MyApp.SecondViewModel"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <StackPanel>
        <DataGrid x:Name="MyList"/>
    </StackPanel>
</Grid>

SecondWindowViewModel:

namespace MyApp
{
[Export(typeof(SecondViewModel))]
class SecondViewModel:Screen
{
    Parse parse = new Parse();
    BindableCollection<MyObject> myList = new BindableCollection<MyObject>();
    MyObject myObject;
    public MyObject MyObject
    {
        get { return myObject; }
        set
        {
            myObject = value;
            NotifyOfPropertyChange(() => MyList);
        }
    }
    public BindableCollection<MyObject> MyList
    {
        get { return myList; }
        set 
        { 
            MyList = value;
            NotifyOfPropertyChange(() => MyList);
        }
    }
    public void GetData(string input)
    {
        string[] tempArray = input.Split();
        List<int> tempList = new List<int>();
        for (int i = 1; i < tempArray.Length; i++)
        {
            if (!string.IsNullOrEmpty(tempArray[i]))
            {
                tempList.Add(Convert.ToInt32(tempArray[i]));
            }
        }
        int[] tempIntArray = tempList.ToArray();
        MyObject = new MyObject(tempArray[0], tempIntArray[0], tempIntArray[1], tempIntArray[2], tempIntArray[3]);
        this.MyList.Add(MyObject);

        Console.WriteLine("MyList has " + MyList.Count.ToString() + " elements.");
        //foreach (MyObject item in MyList)
        //{
        //    Console.WriteLine(item.Location);
        //}
    }
}

}

引导程序:

namespace MyApp
{
    internal class AppBootStrapper : Bootstrapper<IShell>
    {
        static AppBootStrapper()
        {
            //Initializes the logger for debugging, remove or comment out in release.
            LogManager.GetLog = type => new DebugLogger(type);
        }
        private CompositionContainer container;
        protected override void BuildUp(object instance)
        {
            this.container.SatisfyImportsOnce(instance);
        }
        protected override void Configure()
        {
            this.container = 
                new CompositionContainer(
                    new AggregateCatalog(
                        AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
            var batch = new CompositionBatch();
            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(this.container);
            this.container.Compose(batch);
        }
        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return this.container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }
        //This method is required for the BootStrapper.cs to be discovered.
        protected override object GetInstance(Type serviceType, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            IEnumerable<object> exports = this.container.GetExportedValues<object>(contract);
            if (exports.Count() > 0)
            {
                return exports.First();
            }
            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }
    }
}

根据我对Caliburn.Micro的理解,每当observablecollection MyList被更新(添加新项)时,具有x:name MyList的datagrid应该被更新。即使没有数据模板,我认为我应该看到与MyList中对象数量相同的空条目列表。当我在MainViewModel中使用相同的代码而不是绑定到MainView的usercontrol时,渲染列表时没有问题。似乎我错过了关于在视图内更新视图的某些内容。
我应该注意到,我可以通过使用Console.WriteLine(MyList.Count.ToString())并观察输出窗口来验证列表中是否有对象。我讨厌询问这些事情,每次我这样做都会出现打字错误或其他同样愚蠢的问题,但我已经被困在这里太长时间了。
注意:即使在每次迭代中加入MyList.Refresh(),datagrid中也不会发生任何更改。

注意: 看起来这可能回答了我的问题,但我不知道如何实现它。也许如果有人更好地理解它,他们可以将代码行放在我的代码的适当位置,并解释为什么它有效。先谢谢了。Caliburn.Micro约定绑定在嵌套视图中无法正常工作?


为了给出适当的背景:我从.NET 1.1开始使用C#,但从未使用过WPF。如果你卡住了,可以尝试这样做:设置一个非常简单的演示应用程序作为概念验证。代码越少越好。你可能会在那里找到你问题的答案。只是一个想法。 - Pressacco
@Pressaco,感谢您的建议。这本质上是简单演示应用程序的扩展版本。我的第一个版本是一个功能演示,我借鉴了它,然后进行了扩展。在这个迭代中,我正在尝试实现多个视图,以便可以逐步向视图添加功能,而不必担心对整个应用程序的影响。 - spugm1r3
为了给出适当的背景,我已经使用C#大约6个月,WPF大约3个月,Caliburn.Micro几周 :) - spugm1r3
刚刚和一位开发WPF应用程序已经有一段时间的同事交谈了。如果你是WPF新手,他建议看看:www.drwpf.com - Pressacco
你解决这个问题了吗?你能提供两个视图模型的完整代码和两个视图的XAML吗? - Charleh
显示剩余8条评论
1个回答

4
先尝试这种视图模型的方法-我怀疑您的内部视图未被绑定(当应用惯例时,CM不会跨控件边界查找,例如,它不会将惯例应用于嵌套的用户控件)。
<Window x:Class="MyApp.MainWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:cal="http://www.caliburnproject.org"
    xmlns:views="clr-namespace:MyApp"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindowView" Height="300" Width="300">
<Grid>
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="File">
                <MenuItem Header="Open" x:Name="Open"/>
                <MenuItem Header="Exit" x:Name="Exit"/>
            </MenuItem>
        </Menu>
        <StackPanel DockPanel.Dock="Bottom">
            <!-- Use ContentControl for sub-views, CM will do it's magic if you bind to the VM property using the standard conventions -->
            <ContentControl x:Name="SecondView" />
        </StackPanel>
    </DockPanel>
</Grid>

然后在你的主要代码中:

internal class MainWindowViewModel : Screen, IShell
{
    Regex expression = new Regex(@"^N\d\.C\d\.D\d\.R\d:\s\s\s-\d"); //ex. "N1.C1.D2.R1:   -3"      
    // Declare your second VM as a property so you can bind to it via CM conventions  
    public SecondViewModel SecondView 
    { 
        get { return _secondView; } 
        set 
        {
            _secondView = value;
            NotifyOfPropertyChange(() => SecondView);
        }
    }

    public MainWindowViewModel()
    {
        SecondView = new SecondViewModel();
    }

CM会自动将正确的视图注入到内容控件模板中,并设置数据上下文。

或者,您可以使用Bind.Model来将VM实例绑定到视图,这更像是一种以视图为先的方法。

   <StackPanel DockPanel.Dock="Bottom">
        <views:SecondView cal:Bind.Model="{Binding SecondView}" />
    </StackPanel>

我认为应该是 Bind.Model 而不是 View.Model,但我常常混淆这两者,所以如果 Bind.Model 失败,请尝试使用 View.Model。


我尝试过这个方法,但效果和之前一样。表格中的列显示其相应属性的标题,但行从未填充。似乎 SecondView 仅在加载时刷新外观,而不是在底层数据改变时刷新。 - spugm1r3
请检查我在你的问题上最后一条评论,我能想到的唯一可能是你有两个 SecondViewModel 的实例——这可能可以解释。你如何为此设置MEF呢? - Charleh
@spugm1r3,您已开启日志记录,它是在抱怨无法解析视图或视图模型吗? - Derek Beattie
此外,MyObject 上的属性是否会触发 NotifyPropertyChanged? - Derek Beattie
@DerekBeattie,日志中没有关于解析视图或视图模型的任何投诉,这是列表的日志条目:INFO:[2013-04-23T07:11:23.3826619-06:00]绑定约定应用:元素MyList。我在NotifyOfPropertyChange上打了断点,只有MyObject的通知会触发。 - spugm1r3
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接