WPF:实例化一个新的 window/dialog 会导致 >unrelated< 列表绑定停止更新
WPF: Instantiating a new window/dialog causes >unrelated< list binding to stop updating
已解决
我的问题有点奇怪,我什至不确定为什么打开 window 会对我的列表绑定产生任何影响,所以很难解释,所以这里有一个视频展示了什么发生:
https://www.youtube.com/watch?v=bZ03l8OHEY4
我有 2 个页面,起始页和主页。在起始页中,用户可以 select 在 COM 端口上检测到的(支持的)设备之间。单击设备时,程序会读取设备并根据设备上的数据创建视图模型列表(与 BackgroundWorker 异步),这里我还实例化并显示加载 window/dialog。再次单击设备时,程序会转到主页并将视图模型列表显示为为此设备创建的视图列表(在 xaml 中带有 ItemsControl)。
到目前为止一切正常。
但是,如果我有 2 个设备,如果我执行 A 和 B 而不是当我执行 C 时会出现问题。如果我不初始化加载 window/dialog,A 和 B 都可以工作,我最后在 youtube 视频中演示了这一点(如果我实例化但不执行 .Show() 也会中断)。
A) 和我上面说的一样,然后我回到起始页,然后读取并打开另一个设备。 (这是我在 youtube 视频中展示的第二件事)
B) 和我上面说的一样,然后我读取并打开另一个设备而不返回起始页。
C) 相反,在打开其中任何一个之前阅读两个设备(停留在起始页),然后打开第一个设备,然后打开第二个设备。 (这是我在 youtube 视频中展示的第一件事)
Top image is showing that the displayed list is the same as the list that is actually in the memory of the computer. Bottom image shows what happens when doing like described in B. As you can see the list on screen is different from what the LiveVisualTree says the list actually is.
执行A/B时,显示的列表不会更新为其他设备的已创建视图模型列表(仍显示第一个设备的列表)。视图模型列表创建正确(是的,我已经双重和三次检查)视图列表只是没有更新。即使我为列表手动使用 OnPropertyChanged()。
然而,当我使用 C 时,一切都完美无缺。视图列表已更新以显示视图模型列表包含的内容。
一些我认为相关的代码:
用户点击设备:
public static void DeviceClick(ComPortVM comPort)
{
CurrentComPort = comPort;
if (comPort.Device == null)
{
ShowReadingDeviceDialog(true);
BackgroundWorker workThread = new BackgroundWorker();
workThread.DoWork += new DoWorkEventHandler(BackgroundParameterReader_DoWork);
workThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundNewInverter_RunWorkerCompleted);
workThread.RunWorkerAsync(new ReadParameterArgs(basic, false));
}
else
{
OpenDevice();
}
}
OBS:注释掉ShowReadingDeviceDialog(true)时;问题没有发生,一切正常
ShowReadingDeviceDialog(true);
private static void ShowReadingDeviceDialog(bool show)
{
if (ReadingDeviceDialog != null)
{
ReadingDeviceDialog.Close();
}
if (show)
{
MainWindow.instance.IsEnabled = false;
MainWindow.SetCursor(System.Windows.Input.Cursors.Wait);
ReadingDeviceDialog = new OpeningInverterDialog();
ReadingDeviceDialog.Owner = MainWindow.instance;
ReadingDeviceDialog.Show();
}
else
{
MainWindow.SetCursor(System.Windows.Input.Cursors.Arrow);
MainWindow.instance.IsEnabled = true;
}
}
OBS:当不实例化新的 ReadingDeviceDialog 时,问题不会发生并且一切正常
OpenDevice
private static void OpenDevice()
{
if (!isOpening)
{
isOpening = true;
if (!InfoPanels.ContainsKey(CurrentDevice))
{
InfoPanels.Add(CurrentDevice, new InfoPanelVM());
InfoPanels[CurrentDevice].SetUp(CurrentDevice);
}
MainViewModel.instance.RefreshInfoPanel();
startDelay.Interval = TimeSpan.FromMilliseconds(100);
startDelay.Start();
}
}
startDelay Tick
private static void StartDelay_Tick(object sender, EventArgs e)
{
MainWindow.OpenPage("MainPage");
if (!paramTabBars.ContainsKey(CurrentDevice))
{
MainPage.ClearFrame();
paramTabBars.Add(CurrentDevice, new ParamTabBarVM());
ParamTabBar.SetUp(CurrentDevice);
}
else
{
ParamTabBar.OpenTab("");
}
startDelay.Stop();
isOpening = false;
ShowReadDeviceDialog(false);
MainViewModel.instance.RefreshParamTabBtns();
}
是包含导致问题的列表的ParamBarTab,称为ParamTabBtns。使用 MainViewModel.instance.RefreshParamTabBtns();只需对当前 selected 设备使用 OnPropertyChanged(ParamTabBtns),这解决了我遇到的类似问题,即视图列表未更新以适应视图模型列表。但是,当我实例化加载对话框 window.
时,此修复程序现在不起作用
由于在我开始实例化加载对话框之前绑定一直有效,我认为问题不在 XAML,所以我不会费心将它发布到。
我知道这个问题可能很无聊,但由于我不知道发生了什么,所以我真的不知道我应该提供什么其他信息。我主要只是希望有人给我一些想法,比如我应该尝试寻找什么样的东西,但现在我完全失明了。
编辑: 我开始怀疑这是 WPF 中的错误,而不是我这边的错误? DataContext 的 LiveVisualTree 为我提供了正确的列表,但该列表未显示在屏幕上。带绿色箭头的屏幕截图是一切正常的时候,带红色箭头的是我在 A 中所做的那样。旁边的列表说 15 的长度是应该在应用程序中显示的列表,但它不是。
LiveVisualTree 不应该向我展示绑定吗?如果绑定在 LiveVisualTree 中有效,它不应该在应用程序中也有效吗?这不是 LiveVisualTree 的用途吗?调试?那么 LiveVisualTree 中的绑定不应该反映应用程序内部发生的实际绑定吗?
编辑 2: 我在 http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux 之后尝试了 BindingOperations.EnableCollectionSynchronization
然而,这对任何事情都没有影响。所以它可能也与线程无关?无论如何,它与线程相关是没有意义的,因为当对话框 window 在场景 A 和 B 中没有被实例化时,我可以随意更改集合。而且,对话框 window 有注意集合的作用(没有引用,没有方法调用,什么都没有)
LiveVisualTree
编辑 3,解决方案:
显然在使用
时
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
WPF 创建了一个新的数据上下文实例,在我的 MainViewModel 的构造函数中我静态引用了它自己。但是当我实例化一个新的 window 时,该引用改为指向新创建的数据上下文,这破坏了绑定。
为了解决这个问题,我从所有 xaml 文件中删除了上面的代码,而是将其放在构造中(代码隐藏)
DataContext = MainViewModel.instance;
显然在使用
时
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
WPF 创建了一个新的数据上下文实例,在我的 MainViewModel 的构造函数中我静态引用了它自己。但是当我实例化一个新的 window 时,该引用改为指向新创建的数据上下文,这破坏了绑定。为了解决这个问题,我从所有 xaml 文件中删除了上面的代码,而是将其放在构造中(代码隐藏):
DataContext = MainViewModel.instance;
已解决
我的问题有点奇怪,我什至不确定为什么打开 window 会对我的列表绑定产生任何影响,所以很难解释,所以这里有一个视频展示了什么发生: https://www.youtube.com/watch?v=bZ03l8OHEY4
我有 2 个页面,起始页和主页。在起始页中,用户可以 select 在 COM 端口上检测到的(支持的)设备之间。单击设备时,程序会读取设备并根据设备上的数据创建视图模型列表(与 BackgroundWorker 异步),这里我还实例化并显示加载 window/dialog。再次单击设备时,程序会转到主页并将视图模型列表显示为为此设备创建的视图列表(在 xaml 中带有 ItemsControl)。
到目前为止一切正常。 但是,如果我有 2 个设备,如果我执行 A 和 B 而不是当我执行 C 时会出现问题。如果我不初始化加载 window/dialog,A 和 B 都可以工作,我最后在 youtube 视频中演示了这一点(如果我实例化但不执行 .Show() 也会中断)。
A) 和我上面说的一样,然后我回到起始页,然后读取并打开另一个设备。 (这是我在 youtube 视频中展示的第二件事)
B) 和我上面说的一样,然后我读取并打开另一个设备而不返回起始页。
C) 相反,在打开其中任何一个之前阅读两个设备(停留在起始页),然后打开第一个设备,然后打开第二个设备。 (这是我在 youtube 视频中展示的第一件事)
Top image is showing that the displayed list is the same as the list that is actually in the memory of the computer. Bottom image shows what happens when doing like described in B. As you can see the list on screen is different from what the LiveVisualTree says the list actually is.
执行A/B时,显示的列表不会更新为其他设备的已创建视图模型列表(仍显示第一个设备的列表)。视图模型列表创建正确(是的,我已经双重和三次检查)视图列表只是没有更新。即使我为列表手动使用 OnPropertyChanged()。
然而,当我使用 C 时,一切都完美无缺。视图列表已更新以显示视图模型列表包含的内容。
一些我认为相关的代码:
用户点击设备:
public static void DeviceClick(ComPortVM comPort)
{
CurrentComPort = comPort;
if (comPort.Device == null)
{
ShowReadingDeviceDialog(true);
BackgroundWorker workThread = new BackgroundWorker();
workThread.DoWork += new DoWorkEventHandler(BackgroundParameterReader_DoWork);
workThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundNewInverter_RunWorkerCompleted);
workThread.RunWorkerAsync(new ReadParameterArgs(basic, false));
}
else
{
OpenDevice();
}
}
OBS:注释掉ShowReadingDeviceDialog(true)时;问题没有发生,一切正常
ShowReadingDeviceDialog(true);
private static void ShowReadingDeviceDialog(bool show)
{
if (ReadingDeviceDialog != null)
{
ReadingDeviceDialog.Close();
}
if (show)
{
MainWindow.instance.IsEnabled = false;
MainWindow.SetCursor(System.Windows.Input.Cursors.Wait);
ReadingDeviceDialog = new OpeningInverterDialog();
ReadingDeviceDialog.Owner = MainWindow.instance;
ReadingDeviceDialog.Show();
}
else
{
MainWindow.SetCursor(System.Windows.Input.Cursors.Arrow);
MainWindow.instance.IsEnabled = true;
}
}
OBS:当不实例化新的 ReadingDeviceDialog 时,问题不会发生并且一切正常
OpenDevice
private static void OpenDevice()
{
if (!isOpening)
{
isOpening = true;
if (!InfoPanels.ContainsKey(CurrentDevice))
{
InfoPanels.Add(CurrentDevice, new InfoPanelVM());
InfoPanels[CurrentDevice].SetUp(CurrentDevice);
}
MainViewModel.instance.RefreshInfoPanel();
startDelay.Interval = TimeSpan.FromMilliseconds(100);
startDelay.Start();
}
}
startDelay Tick
private static void StartDelay_Tick(object sender, EventArgs e)
{
MainWindow.OpenPage("MainPage");
if (!paramTabBars.ContainsKey(CurrentDevice))
{
MainPage.ClearFrame();
paramTabBars.Add(CurrentDevice, new ParamTabBarVM());
ParamTabBar.SetUp(CurrentDevice);
}
else
{
ParamTabBar.OpenTab("");
}
startDelay.Stop();
isOpening = false;
ShowReadDeviceDialog(false);
MainViewModel.instance.RefreshParamTabBtns();
}
是包含导致问题的列表的ParamBarTab,称为ParamTabBtns。使用 MainViewModel.instance.RefreshParamTabBtns();只需对当前 selected 设备使用 OnPropertyChanged(ParamTabBtns),这解决了我遇到的类似问题,即视图列表未更新以适应视图模型列表。但是,当我实例化加载对话框 window.
时,此修复程序现在不起作用由于在我开始实例化加载对话框之前绑定一直有效,我认为问题不在 XAML,所以我不会费心将它发布到。
我知道这个问题可能很无聊,但由于我不知道发生了什么,所以我真的不知道我应该提供什么其他信息。我主要只是希望有人给我一些想法,比如我应该尝试寻找什么样的东西,但现在我完全失明了。
编辑: 我开始怀疑这是 WPF 中的错误,而不是我这边的错误? DataContext 的 LiveVisualTree 为我提供了正确的列表,但该列表未显示在屏幕上。带绿色箭头的屏幕截图是一切正常的时候,带红色箭头的是我在 A 中所做的那样。旁边的列表说 15 的长度是应该在应用程序中显示的列表,但它不是。 LiveVisualTree 不应该向我展示绑定吗?如果绑定在 LiveVisualTree 中有效,它不应该在应用程序中也有效吗?这不是 LiveVisualTree 的用途吗?调试?那么 LiveVisualTree 中的绑定不应该反映应用程序内部发生的实际绑定吗?
编辑 2: 我在 http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux 之后尝试了 BindingOperations.EnableCollectionSynchronization 然而,这对任何事情都没有影响。所以它可能也与线程无关?无论如何,它与线程相关是没有意义的,因为当对话框 window 在场景 A 和 B 中没有被实例化时,我可以随意更改集合。而且,对话框 window 有注意集合的作用(没有引用,没有方法调用,什么都没有) LiveVisualTree
编辑 3,解决方案: 显然在使用
时 <Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
WPF 创建了一个新的数据上下文实例,在我的 MainViewModel 的构造函数中我静态引用了它自己。但是当我实例化一个新的 window 时,该引用改为指向新创建的数据上下文,这破坏了绑定。 为了解决这个问题,我从所有 xaml 文件中删除了上面的代码,而是将其放在构造中(代码隐藏) DataContext = MainViewModel.instance;
显然在使用
时<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
WPF 创建了一个新的数据上下文实例,在我的 MainViewModel 的构造函数中我静态引用了它自己。但是当我实例化一个新的 window 时,该引用改为指向新创建的数据上下文,这破坏了绑定。为了解决这个问题,我从所有 xaml 文件中删除了上面的代码,而是将其放在构造中(代码隐藏):
DataContext = MainViewModel.instance;