I've got quite a confusing situation:
I open a dialogue that shows a view with an INotifyDataErrorInfo
that immediately returns an error (when the text field is not empty), I see the the red border error notifier:
Opening #1:
I do nothing and close the window, then click the open button again:
Opening #2:
What the heck? I've checked the error flag, it is set. The error border re-appears when I remove the text and write something back, since the error condition checks for string empty? error: no error
Here is a small reproduction case:
Edit: I added the ViewModel back, which is created on every show, causing the INCP change event
MainWindow.xaml.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
namespace LayoutBreakerMinimal
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick( object sender, RoutedEventArgs e )
{
var w = new Window();
var v = Resources["OneInstanceView"] as View; // new View(); <-- would work
w.Content = v;
v.DataContext = new ViewModel();
w.ShowDialog();
}
}
public partial class View : UserControl
{
public View()
{
InitializeComponent();
}
}
public partial class ViewModel : INotifyDataErrorInfo, INotifyPropertyChanged
{
private string _myTextField;
public ViewModel()
{
MyTextField = "Error field";
}
public string MyTextField
{
get { return _myTextField; }
set
{
_myTextField = value;
OnPropertyChanged();
if ( ErrorsChanged != null ) ErrorsChanged( this, new DataErrorsChangedEventArgs( "MyTextField" ) );
}
}
public IEnumerable GetErrors( string propertyName )
{
yield return "Field is null";
}
public bool HasErrors
{
get { return MyTextField != ""; }
}
public event EventHandler< DataErrorsChangedEventArgs > ErrorsChanged;
protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = null )
{
var handler = PropertyChanged;
if ( handler != null ) handler( this, new PropertyChangedEventArgs( propertyName ) );
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
MainWindow.xaml
<Window x:Class="LayoutBreakerMinimal.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:layoutBreakerMinimal="clr-namespace:LayoutBreakerMinimal"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<layoutBreakerMinimal:View x:Key="OneInstanceView" />
</Window.Resources>
<Grid>
<Button Click="ButtonBase_OnClick" Margin="40">Open Dialog, then open it again</Button>
</Grid>
</Window>
View.xaml
<UserControl x:Class="LayoutBreakerMinimal.View"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<TextBox Text="{Binding MyTextField, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True}" Height="30" Width="100"></TextBox>
<Label Content="{Binding HasErrors}" Height="30" Width="100"></Label>
</StackPanel>
</UserControl>
I can't get my head around why the border vanishes.
What I've found out: If I create the view every time new (and not the single resource instance), then the red border is available right from the start every time.
I tested moving the INotifyDataErrorInfo
into a separate ViewModel
, which is instantiated every time new -> No luck.
Edit 2: I added the HasError label to the View to indicate that it keeps displaying error
Solution:
Add x:Shared="False"
as shown below: I have tested the fix and it's working as expected.
<Window.Resources>
<layoutBreakerMinimal:View x:Key="OneInstanceView" x:Shared="False" />
</Window.Resources>
When x:Shared="False"
, modifies WPF resource-retrieval behavior so that requests for the attributed resource create a new instance for each request instead of sharing the same instance for all requests.
Here is the Modified MainWindow.xaml
<Window x:Class="LayoutBreakerMinimal.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:layoutBreakerMinimal="clr-namespace:LayoutBreakerMinimal"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<layoutBreakerMinimal:View x:Key="OneInstanceView" x:Shared="False" />
</Window.Resources>
<Grid>
<Button Click="ButtonBase_OnClick" Margin="40">Open Dialog, then open it again</Button>
</Grid>
</Window>