How do I Access Buttons inside a UserControl from xaml?

后端 未结 6 595
梦如初夏
梦如初夏 2021-01-17 11:51

At work I have several pages, each with buttons in the same places, and with the same properties. Each page also has minor differences. To that end, we created a userControl

6条回答
  •  無奈伤痛
    2021-01-17 12:20

    You could register a Dependency Property Button on your UserControland handle the initialization in its PropertyChangedCallback.

    Template.xaml.cs

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Collections.Generic;
    using System.Windows.Markup.Primitives;
    
    namespace TemplateCode
    {
        public partial class Template : UserControl
        {
            public Template()
            {
                InitializeComponent();
            }
    
            public static readonly DependencyProperty ButtonProperty =
                DependencyProperty.Register("Button", typeof(Button), typeof(Template),
                    new UIPropertyMetadata(new PropertyChangedCallback(ButtonChangedCallback)));
    
            public Button Button
            {
                get { return (Button)GetValue(ButtonProperty); }
                set { SetValue(ButtonProperty, value); }
            }
    
            public static List GetDependencyProperties(Object element)
            {
                List properties = new List();
                MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
                if (markupObject != null)
                {
                    foreach (MarkupProperty mp in markupObject.Properties)
                    {
                        if (mp.DependencyProperty != null)
                        {
                            properties.Add(mp.DependencyProperty);
                        }
                    }
                }
                return properties;
            }
    
            private static void ButtonChangedCallback(object sender, DependencyPropertyChangedEventArgs args)
            {
                // Get button defined by user in MainWindow
                Button userButton     = (Button)args.NewValue;
                // Get template button in UserControl
                UserControl template  = (UserControl)sender;
                Button templateButton = (Button)template.FindName("button");
                // Get userButton props and change templateButton accordingly
                List properties = GetDependencyProperties(userButton);
                foreach(DependencyProperty property in properties)
                {
                    if (templateButton.GetValue(property) != userButton.GetValue(property))
                    {
                        templateButton.SetValue(property, userButton.GetValue(property));
                    }
                }
            }
        }
    }
    

    Template.xaml

    UserControl DataContext is inherited from parent, no need not to set it explicitly

    
    
        
            

    MainWindow.xaml

    You were setting Button.Content instead of Button

    
        
            

    EDIT - Binding Button.Content

    3 ways to do this:

    1. Dependency Properties

    By far the best method. Creating UserControl DP's for every property on the Button is certainly overkill, but for those you want bound to the ViewModel / MainWindow DataContext it makes sense.

    Adding in Template.xaml.cs

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(Template));
    
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }
    

    Template.xaml

    
        
            

    MainWindow.xaml

    
        

    Or

    
        

    Value precedence: UserButton Content > DP Text, so setting the Content in Resources wins.

    2. Creating the Button in your ViewModel

    MVVM purists won't like this, but you could use the Binding mark up instead of StaticResource.

    MainWindow.xaml

    
        
    
    

    3. Setting the binding in code

    As you already noticed, a ViewModel prop (e.g. Txt) can't be referenced in Resources because of the order everything is initialized. You can still do it in code later, but it gets a bit messy with the error to prove.

    System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.Txt; DataItem=null; target element is 'Button' (Name=''); target property is 'Content' (type 'Object')

    Note you need to define the full path on the Content property (setting DataContext on parent won't do).

    MainWindow.xaml

    
        

    Template.xaml.cs

    private static void ButtonChangedCallback(object sender, DependencyPropertyChangedEventArgs args)
    {
        // Get button defined by user in MainWindow
        Button userButton = (Button)args.NewValue;
        // Get template button in UserControl
        UserControl template = (UserControl)sender;
        Button templateButton = (Button)template.FindName("button");
        // Get userButton props and change templateButton accordingly
        List properties = GetDependencyProperties(userButton);
        foreach (DependencyProperty property in properties)
        {
        if (templateButton.GetValue(property) != userButton.GetValue(property))
            templateButton.SetValue(property, userButton.GetValue(property));
        }
        // Set Content binding
        BindingExpression bindingExpression = userButton.GetBindingExpression(Button.ContentProperty);
        if (bindingExpression != null)
            templateButton.SetBinding(Button.ContentProperty, bindingExpression.ParentBinding);
    }
    

提交回复
热议问题