ShowPopupAsync repeatedly binds ViewModel to View
See original GitHub issueOverview
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.
- Create a WPF project using .NET 6
- 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
- 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);
}
}
}
- In your
ShellView.xaml
file, place a Button and bind it toOpenPopup
inShellViewModel.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>
- 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;
}
}
}
- 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.
- Build and run the project
- When the
ShellView
window opens, click on the button that says “Open Popup”, and type a single letter into the TextBox of thePopupView
window.TextBox_TextChanged CALLED
will appear once in the output for Debug. Click the “Close” button in thePopupView
window - 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 - 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:
- Created 2 years ago
- Comments:6 (4 by maintainers)
Top GitHub Comments
Interesting findings, lets leave the issue open I will look at WindowManager and see if I can spot the difference between Popup and Window.
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.