@ S.Kost: А ось це і є проблема. Приберіть діалогове вікно з сетера, у вас виходить рекурсія: значення властивості ще не змінилося, а вже виконалася наступна ітерація event loop'а. Як ви думаєте, яке повинно бути з точки зору WPF значення selected item на час роботи вашого діалогового вікна? - VladD 27 Вересня '16 о 11:19
Проблема полягає в тому, що ви блокуєте сетер діалогом. На час показу діалогу властивість ж повинно мати якесь значення, так що система Binding 'ов виявляється в нокдауні, і працює не так, як хотілося б. Рішення - винести логіку дозволу зміни таба з сетера. Заодно і сетер стане універсально застосовним. Ну і UI-логіку (message box) можна буде вивантажити з VM, що теж непогано.
Отже, дивіться, що потрібно.
По-перше, прибираємо довгі операції з сетера. Сетер повинен бути швидким.
Тепер, нам потрібно заборонити нормальний клік у TabItem 'а, і перенаправити його на наш код. Для такого потрібен якийсь EventTrigger, який би скасував подія. (Наприклад, як тут.) Але ця техніка доставить EventArgs в VM, в якому їм не місце, так що підемо через attached behavior. (Так, це серйозна зброя, іншого я не знайшов.)
Для початку, підключимо через nuget System.Windows.Interactivity.WPF (References → права клавіша миші → Manage NuGet Packages. → Search = System.Windows.Interactivity.WPF). Напишемо Behavior:
Тут все просто: при підключенні підписуємося на PreviewMouseDown у TabItem 'а, при відключенні відписувався, при приході кліка отменіаем стандартну обробку через e.Handled. Чому саме PreviewMouseDown. Тому що ця подія приходить до нас до внутрішніх оброблювачів, і ми можемо скасувати його, не пустивши всередину.
Тепер постає питання, а що робити, коли клік задетектірован? Окей, потрібно викликати команду з VM, а вже VM нехай вирішує, що ж робити далі. Де взяти команду і аргумент? Відповідь очевидна - прикріпити через attached property. Ці attached property можна було б покласти в окремий клас, але можна і засунути в RouteClickBehaviour.
Отримуємо покращений варіант:
Єдина тонкість - ми посилаємо команду асинхронно.
Наступна проблема, а як додати attached behavior через стиль в TabItem. Створювався б у нас TabItem вручну, проблем би не було:
(І команду можна було б передати через параметри). Але у нас стиль, а з доставкою behavior через стиль все складно.
Підемо стандартним обхідним шляхом: через ще одне attached property. Доповнимо RouteClickBehaviour ось чим:
Тепер якщо встановити Inject = true. потрібний behavior навіс автоматично.
Окей, далі - нам потрібна команда в VM, яка буде вирішувати, змінити Tab чи ні. Для команд можна використовувати звичайну RelayCommand. Команду помістимо, зрозуміло, туди ж, де лежить modeChangeExecute:
Окей, і VM-частина готова. Тепер зв'яжемо це все разом через XAML.
Встановлюємо local: RouteClickBehaviour.Inject = True. щоб підключився behavior. Команду потрібно брати з VM для TabControl 'а, т. К. Вирішує питання перемикання саме зовнішня VM. Як параметр передаємо ту локальну VM, яка хоче стати активною.
Перевіряємо. Повинно працювати.
А навіщо взагалі вкладках властивість IsSelected биндить? Обмежтеся SelectedItem. цього буде досить. А з властивостями IsSelected контрол тут без вас розбереться. А якщо вам потрібно в ВМ знати значення цієї властивості, тоді прив'яжіть його через Mode = OneWay
Ось що у мене вийшло. Відразу кажу, я не особливо морочився з архітектурою, просто накидав щоб швидше :)
І у мене все працює відмінно, вкладки виділяються по одній. Проблема у вас швидше за все десь в іншому місці, яке ви не показали, думаючи, що воно не має відношення до проблеми. Шукайте де у вас присвоюється до вкладок IsSelected = true. Або повністю абстрагуйтеся від логіки, залиште тільки ось цю основу, встановивши на інше заглушки і потихеньку підключайте логіку.