question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ShowPopupAsync repeatedly binds ViewModel to View

See original GitHub issue

Overview

Repeatedly calling WindowManager.ShowPopupAsync seems to repeatedly bind the provided ViewModel to its View on top of previous binds. This can lead to a situation where binded methods will be called the same number of times that ShowPopupAsync was called with the same ViewModel.

Platform

  • Windows 11
  • Visual Studio 2022
  • WPF (net.6.0-windows)
  • Caliburn.Micro 4.0.173

Steps to Reproduce

I’ve created a dummy project with everything already set up if you’d like to use that instead. If you choose to use the dummy project, skip ahead to step 7.

  1. Create a WPF project using .NET 6
  2. Create the following project layout:
Project layout
DummyWpfApp/
    ViewModels/
        PopupViewModel.cs
        ShellViewModel.cs
    Views/
        PopupView.xaml  # This is a UserControl
        ShellView.xaml
    App.xaml
    Bootstrapper.cs
  1. Your ShellViewModel.cs file should resemble the following:
public class ShellViewModel : Conductor<object>
    {
        public ShellViewModel()
        {
        }

        private PopupViewModel Popup { get; set; } = new();

        public async void OpenPopup(object sender, RoutedEventArgs e)
        {
            if (Popup.IsActive)
            {
                Debug.WriteLine("OpenPopup: Popup.IsActive = true ... ShowOrHide()");
                Popup.ShowOrHide();
            }
            else
            {
                Debug.WriteLine("OpenPopup: Popup.IsActive = false ... WindowManager()");
                IWindowManager manager = new WindowManager();
                await manager.ShowPopupAsync(Popup);
            }
        }
    }
  1. In your ShellView.xaml file, place a Button and bind it to OpenPopup in ShellViewModel.cs:
<Window x:Class="DummyWpfApp.Views.ShellView"
        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:DummyWpfApp.Views"
        xmlns:cal="http://caliburnmicro.com"
        mc:Ignorable="d"
        Title="ShellView" Height="450" Width="450">
    <StackPanel>
        <Button Content="Open Popup"
                cal:Message.Attach="[Event Click] = [Action OpenPopup($this, $eventargs)]"/>
    </StackPanel>
</Window>
  1. Open the PopupViewModel.cs file and enter the following:
public class PopupViewModel : Screen
    {
        private string _userInput;

        public PopupViewModel()
        {
        }

        public string UserInput
        {
            get => _userInput;
            set
            {
                _userInput = value;
                NotifyOfPropertyChange(() => UserInput);
            }
        }

        public void TextBox_TextChanged(object sender, RoutedEventArgs e)
        {
            Debug.WriteLine("TextBox_TextChanged CALLED");
        }

        public void Close(object sender, RoutedEventArgs e)
        {
            ShowOrHide();
        }

        public void ShowOrHide()
        {
            if (GetView() is Popup popup)
            {
                popup.IsOpen = !popup.IsOpen;
            }
        }
    }
  1. Have your PopupView.xaml file resemble the following:
<UserControl x:Class="DummyWpfApp.Views.PopupView"
             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:DummyWpfApp.Views"
             xmlns:cal="http://caliburnmicro.com"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             Width="100"
             Height="100">
    <StackPanel>
        <TextBox x:Name="UserInput"
                 cal:Message.Attach="[Event TextChanged] = [Action TextBox_TextChanged($this, $eventargs)]"/>
        <Button Content="Close"
                cal:Message.Attach="[Event Click] = [Action Close($this, $eventargs)]"/>
    </StackPanel>
</UserControl>

The most important part in this step is to make sure that the TextBox control’s x:Name directive is set to UserInput so that it binds to the same-name property in the PopupViewModel.cs file.

  1. Build and run the project
  2. When the ShellView window opens, click on the button that says “Open Popup”, and type a single letter into the TextBox of the PopupView window. TextBox_TextChanged CALLED will appear once in the output for Debug. Click the “Close” button in the PopupView window
  3. Repeat step 8, except this time, take note of the number of times TextBox_TextChanged is printed to the Debug output each time you type. It should be 2 times per keystroke
  4. Repeat step 8 again and now TextBox_TextChanged will be printed 3 times per keystroke. For every time you repeat step 8, that is how many times the message will be printed to the output

Expected Behavior

The TextChanged event of a TextBox with an x:Name directive should not be called for each time that WindowManager.ShowPopupAsync has been called with a given ViewModel.

Additional Remarks

I’m not sure if this is the intended behavior. While playing around with WindowManager.ShowWindowAsync, I found that this same behavior doesn’t occur which leads me to believe it’s specific to ShowPopupAsync. Interestingly enough, if you don’t include the x:Name directive for the TextBox, the TextChanged method will only be called once regardless of how many times you’ve called ShowPopupAsync.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
KasperSKcommented, Mar 10, 2022

Interesting findings, lets leave the issue open I will look at WindowManager and see if I can spot the difference between Popup and Window.

1reaction
KasperSKcommented, Mar 6, 2022

I think it might be the binding that causes the TextChanged event to fire twice. I cannot explain why it only happens the second time the popup is opened. I will have a look at it.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How do I create an alert popup from my ViewModel in ...
I'm developing my Xamarin app with the MVVM pattern. I want to display an alert to the user when the user presses a...
Read more >
Issues · Caliburn-Micro/Caliburn.Micro
AllActive child view lifecycle methods ... ShowPopupAsync repeatedly binds ViewModel to View ... Views not found when AssemblySourceCache is installed?
Read more >
[Xamarin.Forms] Get returned value from popup ...
I have a method that opens a Popup with or without a parameter: private async Task InternalShowPopupAsync(Type viewModelType, ...
Read more >
Part 5. From Data Bindings to MVVM - Xamarin
The View and the ViewModel are often connected through data bindings defined in the XAML file. The BindingContext for the View is usually...
Read more >
Dynamic Personal Insurance
Client O Claims View Model . ... ShowPopupAsync(new EditorPopup ... Command="{Binding Source={x:Reference ClientMainViewModel},.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found