如何查找UserControl的子元素而不是Window——替换Window.FindName

3

我目前有一个WPF项目,它有一个主窗口和许多作为该窗口子级的用户控件。该窗口的许多子项是选项卡。我已经成功地用实现几乎完全相同功能的用户控件替换了我的主窗口。

用用户控件替换窗口引入了一个问题 - 目前我们的应用程序根据父窗口使用下面显示的Window.FindName方法确定要显示哪个编程选项卡。因此,我需要用我的主用户控件的适当描述来替换Application.Current.MainWindow。请参见下面出错的C#方法和wpf选项卡实例以获得澄清。

关于Window.FindName()方法的注意事项 - 之所以在我用用户控件替换后它不起作用,是因为FindName方法向上搜索可视树,如此描述

有人知道如何基于x:Name查找用户控件,类似于Application.Current.MainWindow吗?此外,是否有更好的方法查找用户控件,而不是查找x:Name字符串,以防它被重命名?

我们目前如何找到MainWindow - 现在需要找到MainUserControl:

(C#)
private static void SetCurrentProgram(int num)
    {
        Window window = Application.Current.MainWindow;
        ProgrammingTab programmingTab1 = window.FindName("ProgrammingTab1") as ProgrammingTab;
        ProgrammingTab programmingTab2 = window.FindName("ProgrammingTab2") as ProgrammingTab;

        programmingTab1.Visibility = num == 1 ? Visibility.Visible : Visibility.Collapsed;
        programmingTab2.Visibility = num == 2 ? Visibility.Visible : Visibility.Collapsed;

    }

编程选项卡的实例化。
(xaml)
<Grid>
 <ProgrammingControl:ProgrammingTab x:Name="ProgrammingTab1" Program="1" IsVisibleChanged="ProgrammingTab_IsVisibleChanged" />
 <ProgrammingControl:ProgrammingTab x:Name="ProgrammingTab2" Program="2" IsVisibleChanged="ProgrammingTab_IsVisibleChanged" />
</Grid>
3个回答

3

听起来你的应用程序采用了类似WinForms的风格。为了保持这种风格并简单地回答你的问题,你可以使用FindName()方法查找UserControl,然后再次使用它来查找ProgrammingTab,代码如下:

var userControl = (MyUserControl)Application.Current.MainWindow.FindName("userControlName");
var programmingTab1 = (ProgrammingTab)userControl.FindName("ProgrammingTab1");
var programmingTab2 = (ProgrammingTab)userControl.FindName("ProgrammingTab2");
...

然而,我建议您研究使用WPF的高级功能,例如数据绑定。 您可以在由静态属性引用的单例对象上具有一个DependencyProperty“CurrentProgram”,并使用转换器将其简单地数据绑定到可见性。

<ProgrammingTab Visibilty="{Binding CurrentProgram,
   Source={x:Static MyState.Instance},
   Converter={x:Static VisibleIfEqualConverter},
   ConverterParameter=1}" ...>
  ...

通过这个改变,你的SetCurrentProgram变得非常简单:
public void SetCurrentProgram(int num)
{
   MyState.Instance.CurrentProgram = num;
}

这种技术的优点在于,无论在应用程序的哪个位置,任何 ProgrammingTab 都会在 MyState.Instance.CurrentProgram 的值更改时自动出现或消失,无需使用 FindName() 或其他方式查找它们。

非常感谢您的见解 - 我会查看您的WPF建议。然而,我发现以下代码行: var userControl = (MyUserControl)Application.Current.MainWindow.FindName("userControlName"); 最终返回null。我确认了x:Name是正确的。我创建了一个新的主窗口,并在窗口的Grid中实例化了用户控件。有什么建议吗?如果需要,我可以提供更多信息。 - CrimsonX
进一步研究后,我认为问题可能与以下两个方面之一有关: 1)*(MyUserControl)Application.Current.MainWindow.FindName("userControlName");* 某种方式位于错误的 NameScope 中(有关更多信息,请参见 http://msdn.microsoft.com/en-us/library/ms746659.aspx) 或者 2)我正在走错路线,应该使用 VisualTreeHelper:https://dev59.com/questions/snRB5IYBdhLWcg3wZmdz - CrimsonX
你说你在窗口的网格中“实例化了用户控件”。这是通过代码完成的吗?如果是,你是否记得调用RegisterName()方法?系统不会自动为你完成此步骤。如果你是在XAML中进行的操作,那么是否存在任何具有自己NameScope的介入对象呢? - Ray Burns
我想再次强调:按照旧的WinForms方式进行操作感觉很脆弱且容易出错。我真的认为你应该考虑使用数据绑定。 - Ray Burns
我在XAML中将用户控件实例化为<Grid>的子级,因此不需要调用RegisterName()方法。如果我是以程序方式实例化它,您说得对,我必须小心处理这个问题。我会调查一下为什么FindName不像我期望的那样工作,但是我已经能够通过我的答案以程序方式解决了这个问题(尚未更改XAML中的数据绑定)。 - CrimsonX

0
我找到了一个解决这个问题的方法:我基于另一个StackOverflow用户的算法创建了一个新的算法,该算法递归地查找DependencyObject的任何子项。在此处查看我的解决方案。如果你在我之前的帖子中声明FindChild()方法,并将其放在public static class UIHelper {}中,那么你可以通过以下方式解决问题:
  ProgrammingTab programmingTab1 = UIHelper.FindChild<ProgrammingTab>(Application.Current.MainWindow, "ProgrammingTab1");
  ProgrammingTab programmingTab2 = UIHelper.FindChild<ProgrammingTab>(Application.Current.MainWindow, "ProgrammingTab2");

这仍然使用过程式代码而不是声明性XAML进行绑定,就像RayBurns建议的那样。如果它起作用,他的解决方案将更加高效,因为它不会遍历整个树,而只是基于转换器更改选项卡的可见性。我现在将测试该解决方案,并查看其结果。

FindName()无法正常工作的原因在此处的帖子中有描述。



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