【发布时间】:2020-11-27 03:18:56
【问题描述】:
发生的异常是不言自明的:
System.InvalidOperationException: '元素已经有一个逻辑 父母。在附加之前,它必须与旧父级分离 换一个新的。'
但是,它在以下方法的末尾被抛出是没有意义的,该方法是一个事件处理程序,用于更新由检查连接的单独执行线程触发的 NavigationItem(用户控件)中显示的图标状态并在连接状态改变时触发事件(即断开连接 -> 现在连接,反之亦然)。
最后抛出异常的方法:
void OnMachineConnectionStatusChangedHandler(object source, EventArgs e)
{
Dispatcher.Invoke(() =>
{
Machine machine = source as Machine;
// obtain the TreeViewItem representing this machine in the navigation tree
lock (_navigationTree)
{
foreach (TreeViewItem navigationItem in _navigationTree.Items)
{
NavigationItem machineNavItem = navigationItem.Header as NavigationItem;
if (machineNavItem.Machine.Equals(machine))
{
lock (machineNavItem)
{
machineNavItem.Refresh(); // changes shown icon
if (machine.Connected)
{
if (navigationItem.Items.Count > 0)
navigationItem.Items.Clear(); // clear prev. items shown under
TreeViewItem TO = new TreeViewItem()
{
Header = "TO",
Padding = new Thickness(0, 2.5, 10, 2.5)
};
TreeViewItem WO = new TreeViewItem()
{
Header = "WO",
Padding = new Thickness(0, 2.5, 10, 2.5)
};
TreeViewItem EWO = new TreeViewItem()
{
Header = "EWO",
Padding = new Thickness(0, 2.5, 10, 2.5)
};
List<int> machineHolds = machine.getActiveHoldNumbers();
if (machineHolds.Count > 1)
{
foreach (int hold in machineHolds)
{
TreeViewItem holdItem = new TreeViewItem() { Header = ("#" + hold) };
TO.Items.Add(hold);
WO.Items.Add(hold);
EWO.Items.Add(hold);
}
}
navigationItem.Items.Add(TO);
navigationItem.Items.Add(WO);
navigationItem.Items.Add(EWO);
navigationItem.IsExpanded = false;
}
}
}
}
}
this.UpdateLayout();
}); // here, the Exception is thrown
}
我确实使用 VS 进行了调试,单步执行每一行,总体而言,除了最后之外,没有任何东西抛出异常。
如果有任何相关性,这里是 UserControl NavigationItem 的结构供参考:
public partial class NavigationItem : UserControl
{
// fields
private Machine _machine;
// properties
public Machine Machine
{
get { return _machine; }
}
public NavigationItem(Machine machine)
{
InitializeComponent();
_machine = machine;
if (_machine.Connected)
_statusImage.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/Checkmark-Icon.png"));
else
_statusImage.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/X-Icon.png"));
_machineNameLabel.Content = _machine.Name;
}
public void Refresh()
{
if (_machine.Connected)
_statusImage.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/Checkmark-Icon.png"));
else
_statusImage.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/X-Icon.png"));
}
}
非常感谢任何关于为什么抛出和纠正该异常的帮助或指导。
【问题讨论】:
-
你的代码中必须有一些
UIElement至少被使用了两次。每个UIElement只允许添加到可视化树一次。当您编写这样的错误代码时,就会发生这种情况。您通常应该对数据模型而不是视图元素进行操作。这就是 WPF 和 XAML 的强大之处。您应该创建一个数据项集合并将这些项绑定到TreeView。您的数据项应该反映您需要的树结构。 -
然后定义
DataTemplate或HierarchicalDataTemplate来实际布局数据项的可视化UI 外观。您可以将您的用户控件添加到此模板。然后,WPF 会自动创建像TreeViewItem这样的项目容器,并将模板应用于每个容器的外观。这就是每个数据视图的工作方式或应该生成的方式。 Data Templating Overview. -
遵循这个概念,你的代码会变得更干净,感觉更自然,尤其是在编写动态控件时。您将永远再次向您抛出此异常,这意味着您的代码也将变得更加健壮。始终以数据为先。这就是 WPF 和 MVVM 合二为一的原因。
-
@BionicCode 即使没有完整的 MVVM,仅数据绑定就几乎消除了任何出错的可能性。创建和插入 UI 元素来自 ASP.NET 的早期,当时大量文章都以这种方式进行了演示。在当时,你很难理解数据绑定是如何工作的。我震惊了人们仍然这样做。
-
@ATL_DEV 这是误解的结果。尤其是软件开发的初学者不了解或不知道设计模式引入的那些抽象层次。由于您单击按钮,他们对应用程序的感知是 100% UI 驱动的。 UI 必须发光,剩下的就是次要的或者服务于 UI。他们像消费者一样思考。如果你是一个真正的开发者,你就会开始关心像层和模型这样的架构方面——你的感知变成了数据驱动的。很多人很难掌握这个抽象层次,所以他们坚持使用更直观的 UI 驱动“开发”方式。