Search code examples
c#wpfxamlevent-handling

Raise a event on the User control from the Main Window in WPF


There is a textbox in my mainwindow.xaml, when I enter the textbox, I expect the label in my usercontrol, known as View1.xaml will be update accordingly. However I realise the event is not raise at all in the user control when I type the textbox, can you tell me which part is wrong?

The event is able to raise in TextBox_TextChanged_1

my MainWindow.XAML

<Window xmlns:my="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:testapplication"  x:Class="testapplication.MainWindow"
        Title="MainWindow" Height="964" Width="790">
    <Grid >
        <Button x:Name="OpenView1" Content="Open Window 1" HorizontalAlignment="Left" Margin="33,70,0,0" VerticalAlignment="Top" Width="111" RenderTransformOrigin="0.279,1.409" Click="OpenView1_Click"/>
        <Button x:Name="OpenView2" Content="Open Window 2" HorizontalAlignment="Left" Margin="33,169,0,0" VerticalAlignment="Top" Width="111" Click="OpenView2_Click"/>
        <Button x:Name="OpenView3" Content="Open Window 3" HorizontalAlignment="Left" Margin="33,259,0,0" VerticalAlignment="Top" Width="111" Click="OpenView3_Click"/>

        <local:View1 x:Name="ViewOne" HorizontalAlignment="Left" Margin="33,332,0,0" VerticalAlignment="Top" Height="226" Width="204"  Visibility="Hidden"/>
        <local:View2 x:Name="ViewTwo" HorizontalAlignment="Left" Margin="284,332,0,0" VerticalAlignment="Top" Height="226" Width="208" Visibility="Hidden"/>
        <local:View3 x:Name="ViewThree" HorizontalAlignment="Left" Margin="534,332,0,0" VerticalAlignment="Top" Height="226" Width="196" Visibility="Hidden"/>
        <TextBox HorizontalAlignment="Left" Height="42" Margin="326,70,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="182" FontSize="22" TextChanged="TextBox_TextChanged_1"/>

    </Grid>
</Window>

my MainWindow.cs

namespace testapplication
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
           //InitializeComponent();
        }

        //event handler
        public event EventHandler<EventArgs> changedText;

        private void OpenView1_Click(object sender, RoutedEventArgs e)
        {
            ViewOne.Visibility = Visibility.Visible;
        }

        private void OpenView2_Click(object sender, RoutedEventArgs e)
        {
            ViewTwo.Visibility = Visibility.Visible;
        }

        private void OpenView3_Click(object sender, RoutedEventArgs e)
        {
            ViewThree.Visibility = Visibility.Visible;
        }

        private void TextBox_TextChanged_1(object sender, TextChangedEventArgs e)
        {

            if (changedText != null)
            {
               changedText(this, e);
            }
        }


    }
}

This is my UserControl, known as View1.xaml, it is included in my MainWindow.Xaml

namespace testapplication
{
    /// <summary>
    /// Interaction logic for View1.xaml
    /// </summary>
    public partial class View1 : UserControl
    {

        private MainWindow newWindow = new MainWindow();
        public View1()
        {
            InitializeComponent();

            newWindow.changedText += newWindow_ChangeText;
        }

        void newWindow_ChangeText(object sender, EventArgs e)
        {
            ViewOnelabel.Content = "Happy";
        }




    }
}

The problem is my ViewOnelabel.Content = "Happy" did not execute at all, it remain unchanged


Solution

  • There are a few things I would like to point out.

    The equivalent of a winforms label in wpf is a TextBlock. A wpf label is actually a type of contentcontrol. Hence the content property.

    In wpf there are routed events. These "bubble" up ( and tunnel down ) the visual tree. That means you can handle an event in the window from a control in a usercontrol inside it.

    But mainly. I encourage you to look into the MVVM pattern. I've put together some code which illustrates these points. I'd recommend just using binding and mvvm though.

    My MainWindow markup:

    <Window
        Title="MainWindow" Height="350" Width="525"
        
        TextBoxBase.TextChanged="Window_TextChanged"
        
        <Window.DataContext>
        <local:MainWindowViewModel/>
        </Window.DataContext>
        <Grid>
            <StackPanel>
                <Label Name="OutputLabel"/>
                <TextBlock Text="{Binding OutputString}"/>
                <local:UserControl1/>
            </StackPanel>
        
        </Grid>
    </Window>
    

    Notice that it handles a textchanged event and because that's routing it will get the event from UserControl1 inside it. Code behind:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void Window_TextChanged(object sender, TextChangedEventArgs e)
        {
            OutputLabel.Content = $"Happy {((TextBox)e.OriginalSource).Text}";
        }
    }
    

    You don't do anything with the text from your textbox in your handler but I have some code there proves you could get at that from mainwindow if you wanted.

    My viewmodel:

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        private string inputString;
    
        public string InputString
        {
            get { return inputString; }
            set
            {
                inputString = value;
                OutputString = $"{inputString.Length} characters entered";
                RaisePropertyChanged();
            }
        }
    
        private string outputString;
    
        public string OutputString
        {
            get { return outputString; }
            set
            {
                outputString = value;
                RaisePropertyChanged();
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    

    Usercontrol1 just has a textbox:

    <Grid>
        <TextBox Text="{Binding InputString, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
    

    As you type in that textbox, the text is transferred to the bound property in my viewmodel. That hits the code in my setter. This in turn sets OutputString which is bound to my textblock.

    Text changes in both my label and textblock as I type.

    Here's a link to my sample on onedrive https://1drv.ms/u/s!AmPvL3r385QhgpgOPNKPs-veFJ2O3g