dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.08k stars 1.17k forks source link

TabStripPlacement binding exception in TabItem #6440

Open paulrozhkin opened 2 years ago

paulrozhkin commented 2 years ago

namespace TabControlBug { public partial class MainWindow { public MainWindow() { InitializeComponent(); AddItem(); AddItem(); AddItem(); }

    private void Add_OnClick(object sender, RoutedEventArgs e)
    {
        AddItem();
    }

    private void RemoveLast_OnClick(object sender, RoutedEventArgs e)
    {
        if (TabControl.Items.Count > 0)
        {
            TabControl.Items.RemoveAt(TabControl.Items.Count - 1);
        }
    }

    private void RemoveAll_OnClick(object sender, RoutedEventArgs e)
    {
        TabControl.Items.Clear();
    }

    private void AddItem()
    {
        var newItem = new TabItemViewModel
        {
            Header = Guid.NewGuid().ToString().Substring(0, 3),
            Text = Guid.NewGuid().ToString()
        };

        TabControl.Items.Add(newItem);
    }
}

}

TabItemViewModel.cs:

using System.ComponentModel; using System.Runtime.CompilerServices; using TabControlBug.Annotations;

namespace TabControlBug { public class TabItemViewModel : INotifyPropertyChanged { private string _text; private string _header;

    public string Header
    {
        get => _header;
        set
        {
            _header = value;
            OnPropertyChanged(nameof(Header));
        }
    }

    public string Text
    {
        get => _text;
        set
        {
            _text = value;
            OnPropertyChanged(nameof(Text));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

}

to87al commented 2 years ago

When using MVVM, this binding error becomes more evident. Check also Stackoverflow thread

Torsteinws commented 2 years ago

I found an old blogpost that describes a similar problem. The blogpost recommends to globally suppress all binding errors in the app. I did not like this because I only wanted to suppress binding errors that are managed internally by WPF.

Therefore, I ended up writing this workaround for my MVVM app:

Workaround in ViewModel class:

public void RemoveTabItem() 
{
  AppUtils.BindingErrors.Hide();

  // Remove TabItem here

  AppUtils.BindingErrors.Show();
}

Workaround:

namespace AppUtils;

using System.Windows;
using System.Diagnostics;
using System.Windows.Threading;

public static class BindingErrors
{

#if !DEBUG
    public static void Hide() { }
    public static void Show() { }
#endif

#if DEBUG
    private static SourceLevels _defaultLevel = PresentationTraceSources.DataBindingSource.Switch.Level;

    private static bool _isHiding = false;

    public static void Hide()
    {
        if (!_isHiding)
        {
            _isHiding = true;
            _defaultLevel = PresentationTraceSources.DataBindingSource.Switch.Level;
            PresentationTraceSources.DataBindingSource.Switch.Level = SourceLevels.Critical;
        }
    }

    public static void Show()
    {
        if (_isHiding)
        {
            /// Wait for UI to load before showing binding errors again
            Application.Current.Dispatcher.BeginInvoke(() =>
            {
                _isHiding = false;
                PresentationTraceSources.DataBindingSource.Switch.Level = _defaultLevel;
            }, DispatcherPriority.Loaded);
        }
    }
#endif
}