none
WPF中如何获取指定类型的祖先 RRS feed

  • 问题

  • 我自己写了个控件usercontrol1,控件里有两个radiobutton1和radiobutton2以及其他如按钮之类的控件。下面是结构示意

    <UserControl x:Class=" UserControl1" >
        <Grid>
            <Label x:Name="labelTitle"/>
            <Button x:Name="buttonEdit"/>
            <Button x:Name="buttonDelete"/>
            <RadioButton x:Name="radiobutton1"/>
            <RadioButton x:Name="radiobutton2"/>
        </Grid>
    </UserControl>

    现在我需要点击控件中radiobutton1的时候获取到这个usercontrol1的对象,虽然可以连续使用parent来获取到,但假如嵌套的层次再多一些,代码就会很难看,并且只要radiobutton1在控件中的层次改变了,代码就需要改变。

    那么问题来了,WPF中有没有提供从radiobutton1按类型找到usercontrol1的方法?

    2015年7月9日 10:28

答案

  • 一般不建议将UserControl的内部组件暴露出来。因此,这样是不建议的用法:

    uc1.btn1.Click+=btn1_Click;


    1.建议在UserControl中定义事件,分别转发按钮的单击事件,然后在外部仅处理UserControl报告的事件:

    MyUserControl.xaml

            <Button Name="Button1" Click="Button1_OnClick">Button1</Button>
            <Button Name="Button2" Click="Button2_OnClick">Button2</Button>


    MyUserControl.xaml.cs

            private void Button1_OnClick(object sender, RoutedEventArgs e)
            {
                var handler = Button1Click;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
    
            private void Button2_OnClick(object sender, RoutedEventArgs e)
            {
                var handler = Button2Click;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
    
            public event EventHandler Button1Click;
            public event EventHandler Button2Click;
        }

    MainWindow.xaml

            <local:MyControl x:Name="MyControl" Button1Click="MyControl_OnButton1Click"
                             Button2Click="MyControl_OnButton2Click" />

    MainWindow.xaml.cs

            private void MyControl_OnButton1Click(object sender, EventArgs e)
            {
                var msg = string.Format("{0}:ButtonClick", sender);
                MessageBox.Show(msg);
            }
    
            private void MyControl_OnButton2Click(object sender, EventArgs e)
            {
                var msg = string.Format("{0}:Button2Click", sender);
                MessageBox.Show(msg);
            }


    当然,这样的缺点是要定义很多的事件。

    2.因此你可以把事件集中在一起,然后传递不同的参数,然后在外部判断参数,来决定应该进行什么操作:

    MyUserControl.xaml.cs

        public partial class MyControl
        {
            public MyControl()
            {
                InitializeComponent();
            }
    
            private void Button1_OnClick(object sender, RoutedEventArgs e)
            {
                OnButtonClick("This is Button 1");
            }
    
            private void Button2_OnClick(object sender, RoutedEventArgs e)
            {
                OnButtonClick("This is Button 2");
            }
    
            public event EventHandler<ButtonClickEventArgs> ButtonClick;
    
            protected virtual void OnButtonClick(string buttonInfo)
            {
                var handler = ButtonClick;
                var e = new ButtonClickEventArgs {ButtonInfo = buttonInfo};
                if (handler != null)
                {
                    handler(this, e);
                }
            }
        }
    
        public class ButtonClickEventArgs : EventArgs
        {
            public string ButtonInfo { get; set; }
        }

    MainWindow.xaml.cs

            private void MyControl_OnButtonClick(object sender, ButtonClickEventArgs e)
            {
                var msg = string.Format("{0} : {1}", sender, e.ButtonInfo);
                MessageBox.Show(msg);
            }

    3.如果你的UserControl满足下列所有情况:

    a.有大量的按钮或其他的交互元素;

    b.这些按钮或交互元素都需要截然不同的事件处理器(即无法合并);

    c.这些事件处理过程都得在UserControl外部进行;

    d.这些事件处理过程都需要引用UserControl本身。

    那这些按钮或交互元素就不应该被集成到UserControl中。

    UserControl作为一个独立的元素,它应该保持其内部组件的交互过程在内部进行,如果做不到这一点,那就可以考虑将一些组件放到外部以降低对UserControl的内部结构的依赖,或者将外部的一些资源放到内部以保证UserControl能够独立完成工作。



    2015年7月10日 1:41

全部回复

  • 能说说具体需求吗?

    很多时候并不需要查找父级的。

    1.如果你说的Click事件响应是在UserControl里面的,那么可以让UserControl来响应Click事件,然后就能通过sender来获取你的UserControl1。

    如:

    using System.Windows;
    
    namespace WPF_Test
    {
        /// <summary>
        ///     Interaction logic for MyControl.xaml
        /// </summary>
        public partial class MyControl
        {
            public MyControl()
            {
                InitializeComponent();
            }
    
            private void MyControl_OnClick(object sender, RoutedEventArgs e)
            {
                var info = string.Format("{0}\n{1}\n{2}\n{3}\n",
                    sender,
                    e.Source,
                    e.OriginalSource,
                    this);
                MessageBox.Show(info);
            }
        }
    }

    <UserControl x:Class="WPF_Test.MyControl"
                 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"
                 Button.Click="MyControl_OnClick">
        <StackPanel>
            <Button>Button1</Button>
            <Button>Button2</Button>
            <Button>Button3</Button>
            <Button>Button4</Button>
            <Button>Button5</Button>
        </StackPanel>
    </UserControl>

    点击时的效果:

    样例

    2.如果你的Click响应是在UserControl1外部,那么就可以通过e.Source来获取UserControl1。

    3.如果上述两种情景都不符合你的情况,那可以用个笨办法:将每个button的tag设置为UserControl1.

    4.如果上述三种情况都不是你想要的,你可以具体说说你的需求是怎样的,可能有更好的解决办法。

    5.顺便等版主来回答。

    2015年7月9日 15:01
  • 需求跟第二种方法形容的比较相近。

    需求大致是这样:自定义控件UserControl1中包含两个按钮btn1和btn2,这两个按钮需要有不同的响应,响应在UserControl1外部,并且响应函数需要得到btn1和btn2所在的UserControl1的实例。

    以你给出的第一种方法的代码为基础,我贴出修改后能大致描述需求的代码:

    首先UserControl1的xaml,第一个Button设置了名字为btn1:

    <UserControl x:Class="WPFTest.UserControl1"
                 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>
                <Button x:Name="btn1" >Button1</Button>
                <Button>Button2</Button>
                <Button>Button3</Button>
                <Button>Button4</Button>
                <Button>Button5</Button>
            </StackPanel>
        </Grid>
    </UserControl>

    MainWindow的xaml中加入一个UserControl1的实例,设置名称为uc1:

    <Window
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:WPFTest" x:Class="WPFTest.MainWindow"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <local:UserControl1 x:Name="uc1" HorizontalAlignment="Left" Margin="198,44,0,0" VerticalAlignment="Top"/>
        </Grid>
    </Window>

    在MainWindow中对btn1响应,希望能在btn1的响应中得到uc1这个实例:

    namespace WPFTest
    {
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                uc1.btn1.Click+=btn1_Click;
            }
    
            private void btn1_Click(object sender, RoutedEventArgs e)
            {
                var info = string.Format("btn1 response: \n{0}\n{1}\n{2}\n{3}\n", sender, e.Source, e.OriginalSource, this);
                MessageBox.Show(info);
            }
        }
    }

    点击btn1得到的结果:(传不了图片,直接敲结果了)

    btn1 response:

    System.Windows.Controls.Button:Button1

    System.Windows.Controls.Button:Button1

    System.Windows.Controls.Button:Button1

    WPFTest.MainWindow


    2015年7月9日 20:28
  • 一般不建议将UserControl的内部组件暴露出来。因此,这样是不建议的用法:

    uc1.btn1.Click+=btn1_Click;


    1.建议在UserControl中定义事件,分别转发按钮的单击事件,然后在外部仅处理UserControl报告的事件:

    MyUserControl.xaml

            <Button Name="Button1" Click="Button1_OnClick">Button1</Button>
            <Button Name="Button2" Click="Button2_OnClick">Button2</Button>


    MyUserControl.xaml.cs

            private void Button1_OnClick(object sender, RoutedEventArgs e)
            {
                var handler = Button1Click;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
    
            private void Button2_OnClick(object sender, RoutedEventArgs e)
            {
                var handler = Button2Click;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
    
            public event EventHandler Button1Click;
            public event EventHandler Button2Click;
        }

    MainWindow.xaml

            <local:MyControl x:Name="MyControl" Button1Click="MyControl_OnButton1Click"
                             Button2Click="MyControl_OnButton2Click" />

    MainWindow.xaml.cs

            private void MyControl_OnButton1Click(object sender, EventArgs e)
            {
                var msg = string.Format("{0}:ButtonClick", sender);
                MessageBox.Show(msg);
            }
    
            private void MyControl_OnButton2Click(object sender, EventArgs e)
            {
                var msg = string.Format("{0}:Button2Click", sender);
                MessageBox.Show(msg);
            }


    当然,这样的缺点是要定义很多的事件。

    2.因此你可以把事件集中在一起,然后传递不同的参数,然后在外部判断参数,来决定应该进行什么操作:

    MyUserControl.xaml.cs

        public partial class MyControl
        {
            public MyControl()
            {
                InitializeComponent();
            }
    
            private void Button1_OnClick(object sender, RoutedEventArgs e)
            {
                OnButtonClick("This is Button 1");
            }
    
            private void Button2_OnClick(object sender, RoutedEventArgs e)
            {
                OnButtonClick("This is Button 2");
            }
    
            public event EventHandler<ButtonClickEventArgs> ButtonClick;
    
            protected virtual void OnButtonClick(string buttonInfo)
            {
                var handler = ButtonClick;
                var e = new ButtonClickEventArgs {ButtonInfo = buttonInfo};
                if (handler != null)
                {
                    handler(this, e);
                }
            }
        }
    
        public class ButtonClickEventArgs : EventArgs
        {
            public string ButtonInfo { get; set; }
        }

    MainWindow.xaml.cs

            private void MyControl_OnButtonClick(object sender, ButtonClickEventArgs e)
            {
                var msg = string.Format("{0} : {1}", sender, e.ButtonInfo);
                MessageBox.Show(msg);
            }

    3.如果你的UserControl满足下列所有情况:

    a.有大量的按钮或其他的交互元素;

    b.这些按钮或交互元素都需要截然不同的事件处理器(即无法合并);

    c.这些事件处理过程都得在UserControl外部进行;

    d.这些事件处理过程都需要引用UserControl本身。

    那这些按钮或交互元素就不应该被集成到UserControl中。

    UserControl作为一个独立的元素,它应该保持其内部组件的交互过程在内部进行,如果做不到这一点,那就可以考虑将一些组件放到外部以降低对UserControl的内部结构的依赖,或者将外部的一些资源放到内部以保证UserControl能够独立完成工作。



    2015年7月10日 1:41
  • 了解了,最后我是采用在UserControl内部响应RadioButton事件来完成的。

    提这个问题是因为先前的思路,就是如前面所说的想要在UserControl外部(MainWindow)中响应UserControl中的RadioButton,并且还要获取到这个UserControl的实例。因为自己尝试没成功,所以发帖询问。你给出的方法能实现我先前的思路,学习了。

    非常感谢!

    2015年7月10日 15:44