IndexOutOfRangeException при изменении выбранного TabItem дважды - PullRequest
0 голосов
/ 29 августа 2011

У меня есть следующее простое WPF-приложение:

<Window x:Class="TabControlOutOfRangeException.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <TabControl ItemsSource="{Binding ItemsSource}"
                SelectedIndex="{Binding SelectedIndex, IsAsync=True}" />
</Window>

со следующим простым кодом:

using System.Collections.Generic;

namespace TabControlOutOfRangeException
{
    public partial class MainWindow
    {
        public List<string> ItemsSource { get; private set; }
        public int SelectedIndex { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            ItemsSource = new List<string>{"Foo", "Bar", "FooBar"};

            DataContext = this;
        }
    }
}

Когда я нажимаю на второй вкладке («Панель»),ничего не отображается.Когда я снова нажимаю на любую вкладку, я получаю исключение IndexOutOfRangeException.Если для параметра IsAsync установлено значение False, TabControl работает.

К сожалению, у меня есть требование запросить у пользователя "Сохранить изменения?"Вопрос, когда он покинет текущую вкладку.Поэтому я хотел установить SelectedIndex обратно к старому значению в set-свойстве.Очевидно, это не работает.Что я делаю не так?

Обновление

Я вложил в TabControl злой хак, и он работает для меня.Вот код MainWindow.xaml:

<Window x:Class="TabControlOutOfRangeException.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:TabControlOutOfRangeException="clr-namespace:TabControlOutOfRangeException" Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TabControlOutOfRangeException:PreventChangingTabsTabControl
            ItemsSource="{Binding ItemsSource}"
            SelectedIndex="{Binding SelectedIndex}"
            CanChangeTab="{Binding CanChangeTab}" Margin="0,0,0,51" />
        <CheckBox Content="CanChangeTab" IsChecked="{Binding CanChangeTab}" Margin="0,287,0,0" />
    </Grid>
</Window>

А вот MainWindow.xaml.cs:

using System.Collections.Generic;
using System.ComponentModel;

namespace TabControlOutOfRangeException
{
    public partial class MainWindow : INotifyPropertyChanged
    {
        public int SelectedIndex { get; set; }
        public List<string> ItemsSource { get; private set; }

        public MainWindow()
        {
            InitializeComponent();

            ItemsSource = new List<string> { "Foo", "Bar", "FooBar" };

            DataContext = this;
        }

        private bool _canChangeTab;
        public bool CanChangeTab
        {
            get { return _canChangeTab; }
            set
            {
                _canChangeTab = value;
                OnPropertyChanged("CanChangeTab");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string property)
        {
            var handler = PropertyChanged;

            if (handler != null)
                handler(this, new PropertyChangedEventArgs(property));
        }
    }
}

И, наконец, подклассный TabControl:

using System;
using System.Windows;
using System.Windows.Controls;

namespace TabControlOutOfRangeException
{
    public class PreventChangingTabsTabControl : TabControl
    {
        private int _previousTab;

        public PreventChangingTabsTabControl()
        {
            SelectionChanged += (s, e) =>
            {
                if (!CanChangeTab)
                {
                    e.Handled = true;
                    SelectedIndex = _previousTab;
                }
                else
                    _previousTab = SelectedIndex;
            };
        }

        public static readonly DependencyProperty CanChangeTabProperty = DependencyProperty.Register(
            "CanChangeTab",
            typeof(Boolean),
            typeof(PreventChangingTabsTabControl)
        );

        public bool CanChangeTab
        {
            get { return (bool)GetValue(CanChangeTabProperty); }
            set { SetValue(CanChangeTabProperty, value); }
        }
    }
}

Ответы [ 4 ]

1 голос
/ 29 августа 2011

Я бы рассмотрел редизайн этого окна вместо введения кучи новых проблем, просто путем проб и ошибок в свойстве привязки IsAsync.

Я не уверен, разрешит ли элемент управления с вкладками этот уровень управления, который вы ищете. Вы можете попытаться поймать событие, когда кто-то попытается изменить выбранный элемент, но вы не сможете отменить его. Однако есть способ, см. Вариант 4, если вы не хотите читать другие предложения.

Вариант 1: пользовательский элемент управления

Я хотел бы написать немного пользовательского кода, который имитирует функциональность контейнера элемента. Так легко добиться желаемого поведения. Просто привяжите команду к кнопкам (или к любому другому элементу управления, на котором вы хотите, чтобы пользователь нажал) и верните CanExecute со значением false, если все еще есть изменения, которые нужно отправить - или спросите своего пользователя, что вы хотите, когда он получит выполняется и изменяет только отображаемое содержимое (т. е. пользовательский «TabItem»), если необходимо.

Вариант 2: предотвращение пользователя путем отключения вкладок

Другим способом было бы связать свойство «IsEnabled» каждой из вкладок со свойством зависимости в вашей модели представления, которое определяет, какая из них доступна пользователю. Мол, вы знаете, что первая страница все еще нуждается в работе, просто отключите все остальные. Но имейте в виду, что сейчас вы не создаете никаких TabItems - ваш контент представляет собой просто строки.

public List<TabItem> ItemsSource { get; private set; }

....

ItemsSource = new List<TabItem> { new TabItem() { Header = "Foo", Content = "Foo" }, new TabItem() { Header = "Bar", Content = "Bar" }, new TabItem() { Header = "FooBar", Content = "FooBar" } };

Поскольку вы не хотите, чтобы пользователь что-то делал, а хотели бы попросить сохранить изменения, я бы выбрал собственный маршрут управления. Еще есть вариант 3.

Вариант 3: всплывающее окно

Используйте всплывающее окно и попросите сохранить изменения, если пользователь закончил с изменением того, что находится на этой странице, и нажмет кнопку «Закрыть» (вместо кнопки «Сохранить», которая также должна находиться на той же странице;) )

Вариант 4: проверка на StackOverflow

На самом деле я сделал это для вас, и вот решение, которое другой пользователь нашел для той же самой проблемы: Управление вкладками WPF Предотвращение изменения вкладок Причина, по которой я не опубликовал это заранее, состояла в том, что я лично не сделал бы это таким образом, потому что, человек, я ненавижу приложения, которые делают это.

Вот, пожалуйста.

0 голосов
/ 29 августа 2011

Если вы хотите изменить selectedIndex в самом установщике, а затем обновить его в пользовательском интерфейсе, вы должны вызвать свойство, измененное асинхронно, как это -

public partial class MainWindow : INotifyPropertyChanged

private int _selectedIndex;
public int SelectedIndex
{
    get { return _selectedIndex; }
    set
    {
        if (_selectedIndex != value)
        {
            _selectedIndex = value;
            OnPropertyChangedAsAsync("SelectedIndex");
        }
    }
}

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

protected virtual void OnPropertyChangedAsAsync(string propertyName)
{
    Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate {  OnPropertyChanged(propertyName); }, DispatcherPriority.Render, null);
}
0 голосов
/ 29 августа 2011

Если вы хотите иметь возможность влиять на TabControl, привязка должна быть двухсторонней, т. Е. Ваш программный код должен иметь возможность уведомлять представление о том, что свойство изменилось, для этого вам следует реализовать INotifyPropertyChanged в вашем окне, например

public partial class MainWindow : INotifyPropertyChanged
private int _selectedIndex;
public int SelectedIndex
{
    get { return _selectedIndex; }
    set
    {
        if (_selectedIndex != value)
        {
            _selectedIndex = value;
            OnPropertyChanged("SelectedIndex");
        }
    }
}

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
    if (this.PropertyChanged != null)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Асинхронные привязки обычно предназначены для свойств, которые имеют длительный геттер, например, с. запрос к базе данных, вам не нужно это здесь.

0 голосов
/ 29 августа 2011

Попробуйте на самом деле реализовать SelectedIndex

    namespace TabControlOutOfRangeException
    {
        public partial class MainWindow  
        {
            public List<string> ItemsSource { get; private set; }
            private int selectedIndex

            public int SelectedIndex { 
                get { return selectedIndex; } 
                set { selecectedIndex = value; } }

            public MainWindow()
            {
                InitializeComponent();

                ItemsSource = new List<string>{"Foo", "Bar", "FooBar"};

                DataContext = this;
            }
        }
    }
...