0

我有以下简单的 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

4 回答 4

1

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

我不确定选项卡控件是否允许您寻求这种级别的控制。当有人试图更改所选项目时,您可以尝试捕捉该事件,但您无法取消它。但是,有一种方法,如果您不想阅读其他建议,请参阅选项 4。

选项 1:自定义控件

我会考虑编写一些自定义代码来模仿项目容器的功能。通过这种方式很容易实现您想要的行为。只需将命令绑定到按钮(或您希望用户单击的任何控件),如果仍有更改要提交,则返回CanExecute并返回 false - 或者在执行时询问您的用户您想要的任何内容,并且只更改如果需要,显示内容(即您的自定义“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 我没有预先发布的原因是我个人不会那样做,因为,伙计我讨厌这样做的应用程序吗?

干得好。

于 2011-08-29T16:15:45.527 回答
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;
            }
        }
    }
于 2011-08-29T13:32:20.757 回答
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 的属性,例如数据库查询,您在这里不应该需要它。

于 2011-08-29T14:20:26.213 回答
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);
}
于 2011-08-29T17:17:11.133 回答