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

2 番目のタブ (「バー」) をクリックすると、何も表示されません。任意のタブをもう一度クリックすると、IndexOutOfRangeException が発生します。IsAsync を False に設定すると、TabControl が機能します。

残念ながら、ユーザーに「変更を保存しますか?」と問い合わせる必要があります。彼が現在のタブを離れたときの質問。そこで、set-property 内で 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); }
        }
    }
}
4

4 に答える 4

1

バインディングの「IsAsync」プロパティで試行錯誤するだけで、新しい問題の山を導入するのではなく、そのウィンドウの再設計を検討します。

タブ コントロールが、あなたが求めているこのレベルのコントロールを許可するかどうかはわかりません。誰かが選択したアイテムを変更しようとしたときにイベントをキャッチしようとすることはできますが、キャンセルすることはできません。ただし、他の提案を読みたくない場合は、オプション 4 を参照してください。

オプション 1: カスタム コントロール

アイテム コンテナーの機能を模倣するカスタム コードを少し書くことを検討します。このようにして、目的の動作を簡単に実現できます。コマンドをボタン (またはユーザーにクリックさせたい任意のコントロール) にバインドし、送信する変更がまだある場合はCanExecuteを false で返すか、実行時に必要なものをユーザーに尋ねて、必要に応じて表示されるコンテンツ (つまり、カスタムの「TabItem」)。

オプション 2: タブを無効にしてユーザーを防止する

別の方法は、各タブアイテムの「IsEnabled」プロパティをビューモデルの依存関係プロパティにバインドし、ユーザーが使用できるタブアイテムを制御することです。同様に、最初のページにはまだ作業が必要であることがわかっています。その間、他のすべてのページを無効にしてください。ただし、現時点では TabItem を作成していないことに注意してください。コンテンツは単なる文字列です。

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 を変更したい場合は、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 に答える
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

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 に答える