Дві активні вкладки tabcontrol або як скасувати зміну вкладки stack overflow російською

@ 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. Або повністю абстрагуйтеся від логіки, залиште тільки ось цю основу, встановивши на інше заглушки і потихеньку підключайте логіку.

Схожі статті