我有以下简单的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); }
}
}
}
答案 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);
}