更改选定的TabItem两次时的IndexOutOfRangeException

时间:2011-08-29 13:06:04

标签: wpf asynchronous tabcontrol outofrangeexception

我有以下简单的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-property中的旧值。显然这不起作用。我做错了什么?

更新

我已经将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 个答案:

答案 0 :(得分:1)

我会考虑重新设计该窗口,而不是通过对绑定的“IsAsync”属性进行试错来引入一堆新问题。

我不确定标签控件是否允许您寻求此级别的控制。当有人试图更改所选项目时,您可能会尝试捕获该事件,但您无法将其取消。但是,如果您不想阅读其他建议,请参阅选项4。

选项1:自定义控件

我会考虑编写一些模仿项容器功能的自定义代码。以这种方式轻松实现您想要的行为。只需将命令绑定到按钮(或您希望用户单击的任何控件),如果仍有更改要提交,则返回 CanExecute ,或者在用户提出任何需要时向其询问如果需要,只会更改显示的内容(即您的自定义“TabItem”)。

选项2:通过停用标签来阻止用户

另一种方法是将每个tabitems的“IsEnabled”属性绑定到viewmodel上的依赖项属性,该属性控制哪些属性可供用户使用。就像,你知道第一页仍然需要工作,同时禁用所有其他页面。但请注意,现在您没有创建任何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 Tab Control Prevent Tab Change 我没有预先发布的原因是我本人不会这样做,因为,男人我会讨厌这样做的应用程序。

你走了。

答案 1 :(得分:0)

尝试实际实现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;
            }
        }
    }

答案 2 :(得分:0)

如果您希望能够影响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));
    }
}

异步绑定通常用于具有长时间运行的getter的属性,例如数据库查询,你不应该在这里需要它。

答案 3 :(得分:0)

如果你想在setter中更改selectedIndex,然后在UI上更新它,你必须以异步方式改变属性,如下所示 -

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);
}