两次更改所选 TabItem 时出现 IndexOutOfRangeException
我有以下简单的 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;
}
}
}
当我单击第二个选项卡(“Bar”)时,不会显示任何内容。当我再次单击任何选项卡时,我收到 IndexOutOfRangeException。将 IsAsync 设置为 False,TabControl 可以工作。
不幸的是,我需要向用户询问“保存更改吗?”当他离开当前选项卡时询问。所以我想将 SelectedIndex 设置回设置属性中的旧值。显然这是行不通的。我做错了什么?
更新
我已经用邪恶的黑客对 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); }
}
}
}
I have the following simple WPF-app:
<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>
with following simple code-behind:
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;
}
}
}
When I click on the second tab ("Bar"), nothing is displayed. When I click again on any tab, I get an IndexOutOfRangeException. Setting IsAsync to False, the TabControl works.
Unfortunately, I have the requirement to query the user a "Save changes?" question when he leaves the current tab. So I wanted to set the SelectedIndex back to the old value within the set-property. Obviously this doesn't work. What am I doing wrong?
Update
I've subclassed the TabControl with the evil hack and it works for me. Here is the code of 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>
And here 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));
}
}
}
And finally the subclassed 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); }
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我会考虑重新设计该窗口,而不是通过对绑定的“IsAsync”属性进行反复试验来引入一堆新问题。
我不确定选项卡控件是否允许您寻求这种级别的控制。当有人尝试更改所选项目时,您可以尝试捕获该事件,但您无法将其取消。但是,如果您不想阅读其他建议,请参阅选项 4。
选项 1:自定义控件
我会考虑编写一些模仿项目容器功能的自定义代码。通过这种方式很容易实现您想要的行为。只需将命令绑定到按钮(或您希望用户单击的任何控件),如果仍有需要提交的更改,则返回 CanExecute 为 false - 或者询问用户您想要的任何内容被执行,并且仅在需要时更改显示的内容(即您的自定义“TabItem”)。
选项 2:通过禁用选项卡来阻止用户
另一种方法是将每个选项卡项的“IsEnabled”属性绑定到视图模型上的依赖属性,该属性控制哪些选项卡可供用户使用用户。就像,您知道第一页仍然需要工作,只需同时禁用所有其他页面即可。但请注意,现在您没有创建任何 TabItem - 您的内容只是纯字符串。
由于您不想阻止用户执行某些操作,而是希望要求保存更改,所以我会选择自定义控制路线。仍然有选项 3。
选项 3:弹出窗口
如果用户完成更改该页面上的任何内容并单击“关闭”按钮(而不是单击“关闭”按钮),则使用弹出窗口并要求保存更改“保存”按钮也应该位于同一页面上;) )
选项 4:检查 StackOverflow
实际上,我为您做到了这一点,这是另一个用户为完全相同的问题找到的解决方案:<一href="https://stackoverflow.com/questions/5090392/wpf-tab-control-prevent-tab-change">WPF 选项卡控件防止选项卡更改
我没有预先发布该内容的原因是我个人不会这样做,因为我讨厌这样做的应用程序。
干得好。
I'd consider a redesign of that window instead of introducing a heap of new problems by just trial-and-erroring on the "IsAsync" property of the binding.
I am not sure if a tab control will allow this level of control you seek. You could try to catch the event when someone tries to change the selected item, but you would not be able to cancel it out. There is a way however, see Option 4 if you dont want to read the other suggestions.
Option 1: The custom control
I would consider writing a bit of custom code that mimics the functionality of an item container. Its easy to achieve your desired behaviour this way. Just bind a command to the buttons (or whatever control you wish the user to click on), and return CanExecute with false if there are still changes to be submitted - or ask your user whatever you want when it gets executed, and only change the content displayed (ie your custom "TabItem") if desired.
Option 2: Preventing the user by disabling the tabs
Another way would be to bind the "IsEnabled" property of each of the tabitems to a dependency property on your viewmodel, that controls which of them is available to the user. Like, you know that the first page still needs work, just disable all the other ones meanwhile. But be aware that right now you are not creating any TabItems - your content are just plain strings.
Since you don't want to prevent the user doing something but rather would like to ask to save the changes, i'd go for the custom control route. Still there is option 3.
Option 3: Popup window
Use a popup window and ask to save changes if the user is finished with changing whatever is on that page and clicks on the "Close" button (rather than the "Save" button that should also reside on the same page ;) )
Option 4: Check on StackOverflow
Actually i did that for you, and here is a solution another user has found for the exact same problem: WPF Tab Control Prevent Tab Change
The reason why i didnt post that up-front was that i personally wouldnt do it that way because, man do i HATE applications that do this.
Here you go.
尝试实际实现 SelectedIndex
Try actually implementing the SelectedIndex
如果您希望能够影响 TabControl,则绑定需要是双向的,即您的代码隐藏需要能够通知视图属性已更改,为此您应该实现
INotifyPropertyChanged
在您的窗口中,例如异步绑定通常用于具有长时间运行的 getter 的属性,例如数据库查询,您在这里不应该需要它。
If you want to be able to affect the TabControl the binding needs to be two-way, i.e. your code-behind needs to be able to notify the view that the property changed, for that you should implement
INotifyPropertyChanged
in your window, e.g.Async bindings are usually for properties which have a long-running getter, with e.g. a database query, you should not need this here.
如果您想更改 setter 本身中的 selectedIndex,然后要在 UI 上更新它,您必须以异步方式引发更改的属性,如下所示 -
In case you want to to change the selectedIndex in the setter itself, then to get it updated on UI, you have to raise the property changed in an async manner like this -