m.nelson0100
m.nelson0100

Reputation: 47

WPF C# user control with 2 or more buttons

Sorry if this has been asked before and I have spent about a week trying to find a similar question to point me in the right direction. I am teaching myself C# with WPF, XAML etc and am playing around with user controls. I made a simple app with a user control to load on top of other windows or user controls. The UC in question has two buttons and I need to get to the click events for each button in main window once the control is loaded. The main window has a button that loads the control.

Through some research I was able to find a solution from user SWilko (https://stackoverflow.com/a/28949666/10659981) but I can't figure it out for each button separately (click button a and show "clicked btn a", click button b and show "clicked button b"). I did try calling by sender using name and that will not work either. I feel like I am close with the help from the answer by SWilko but stuck.

Here is the code so far: Basic main screen loading user control

    <Window x:Class="UCBTN_TEST.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:UCBTN_TEST"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="435">
        <Grid>
            <Button Content="Load Button" HorizontalAlignment="Left" Margin="18,23,0,0" VerticalAlignment="Top" Width="74" Click="Button_Click"/>
            <Grid x:Name="GridLoad" HorizontalAlignment="Left" Height="300" Margin="120,23,0,0" VerticalAlignment="Top" Width="300" Background="#FFF1CBCB"/>
    
        </Grid>
    </Window>
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace UCBTN_TEST
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                GridLoad.Children.Clear();
                GridLoad.Children.Add(new WindowControl());
            }
        }
    }

The button user control

    <UserControl x:Name="UCMain" x:Class="UCBTN_TEST.Controls.ButtonControl"
                 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" 
                 xmlns:local="clr-namespace:UCBTN_TEST.Controls"
                 mc:Ignorable="d" d:DesignWidth="300" Height="40.333">
        
        <Grid Background="#FFE7EEA7">
            <Button x:Name="ButtonA" Content="Button A" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75" Click="ButtonA_Click" Background="Red"/>
            <Button x:Name="ButtonB" Content="Button B" HorizontalAlignment="Left" Margin="215,10,0,0" VerticalAlignment="Top" Width="75" Click="ButtonA_Click" Background="Green"/>
        </Grid>
    </UserControl>
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace UCBTN_TEST.Controls
    {
        /// <summary>
        /// Interaction logic for ButtonControl.xaml
        /// </summary>
        public partial class ButtonControl : UserControl
        {
            public ButtonControl()
            {
                InitializeComponent();
            }
    
            private void ButtonA_Click(object sender, RoutedEventArgs e)
            {
                RaiseEvent(new RoutedEventArgs(ClickEvent1, this));
            }
    
            public static readonly RoutedEvent ClickEvent1 = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ButtonControl));
    
            public event RoutedEventHandler Click
            {
                add { AddHandler(ClickEvent1, value); }
                remove { RemoveHandler(ClickEvent1, value); }           
            }
        }
    }

A second user control which would ultimately have some other controls once the buttons work correctly. But the button UC will load on top, simple button features related to WindowControl.

    <UserControl
                 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" 
                 xmlns:local="clr-namespace:UCBTN_TEST"
                 xmlns:Controls="clr-namespace:UCBTN_TEST.Controls" x:Class="UCBTN_TEST.WindowControl"
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300">
    
        <Grid Background="#FFE7CFEE">
            <Controls:ButtonControl HorizontalAlignment="Left" Height="37" VerticalAlignment="Top" Width="300" Click="Click1"/>
        </Grid>
    
    </UserControl>

I understand the behind code and why this is happening. My problem is that I need to have the buttons be unique in their events. I have tried calling by sender and name and that just kills the event all together.

    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using UCBTN_TEST.Controls;
    
    namespace UCBTN_TEST
    {
        /// <summary>
        /// Interaction logic for WindowControl.xaml
        /// </summary>
        public partial class WindowControl : UserControl
        {
    
            public WindowControl()
            {
                InitializeComponent();            
            }
    
            private void Click1(object sender, RoutedEventArgs e)
            {
                
                MessageBox.Show("This triggers both");
                
            }
        }
    }

Upvotes: 1

Views: 1531

Answers (1)

Andy
Andy

Reputation: 12276

I was going to add a bunch of comments but really this is kind of answering the question and there's a lot to explain.

You should look into MVVM and mostly be thinking in terms of binding commands rather than which button was clicked. There are exceptions to this. For example, if you were building an on screen keyboard. The reason this is different because it's purpose can be encapsulated. The user presses a button which has "A" in it. Whatever textbox is focussed should be sent the character "A". They press a button showing "B" and similarly "B" should be sent. That functionality can be encapsulated in the control.

As it is, you have two buttons.

You put them in a usercontrol and encapsulate them.

By doing this you created a boundary.

This then creates a complication - which was clicked?

The usercontrol is also not particularly re-use friendly. If you add two then there are two buttonA and two button B. You could potentially improve that with a custom event args on your custom routed event and a dependency property on your usercontrol. Pass some usercontrol identifier along with which button was pressed.

This would be an unusual way to work though. I've rarely seen Custom routed events used in commercial apps.

All in all I would suggest the usercontrol mainly adds complexity.

Say you wanted to have 20 sets of 2 buttons.

Or 20 sets of 5 radiobuttons for a set of multiple choice questions.

The way to do that sort of thing is to use an itemscontrol and template out the multiple controls. One template giving 2 buttons ( or a textblock question and 5 radiobuttons for answers ) per row.

A click event is already a routed event and would bubble to the window. You may as well remove your custom routed event and the handler out the usercontrol... and the usercontrol. Just handle click in the window.

Code:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var btn = e.OriginalSource as Button;
        if(btn == null)
        {
            return;
        }

        MessageBox.Show($"Button clicked was {btn.Tag}");
    }

Markup:

    ButtonBase.Click="Button_Click"
    Title="MainWindow" 
        >
    <Grid>
        <StackPanel>
            <Button x:Name="ButtonA" Content="Button A" Tag="A" Background="Red"/>
            <Button x:Name="ButtonB" Content="Button B" Tag="B" Background="Green"/>
        </StackPanel>
    </Grid>
    </Window>

Upvotes: 1

Related Questions