RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 567880
Accepted
Gardes
Gardes
Asked:2020-09-19 17:07:00 +0000 UTC2020-09-19 17:07:00 +0000 UTC 2020-09-19 17:07:00 +0000 UTC

两个活动的标签 TabControl 或如何取消标签的改变?

  • 772

提供TabControl3 个标签TabItem。每个选项卡都有自己的VM. 整个东西都保存在收藏中ObservableCollection<AbstractVM> TabItemsVM。

  <TabControl ItemsSource="{Binding TabItemsVM}"
              SelectedItem="{Binding SelectedTabVM}"
              TabStripPlacement="Top"> 
        <TabControl.ItemContainerStyle>
            <Style TargetType="TabItem">
                <Setter Property="Header" Value="{Binding Header}"/>
                <Setter Property="Width" Value="100"/>                                       
            </Style>
        </TabControl.ItemContainerStyle>
    </TabControl>   

作品

通过分配从代码中选择另一个选项卡时SelectedTabVM TabItemsVM[0]。代码如下:

_selectedTabVM = TabItemsVM[0]; //присваиваю именно приватному полю,
                                //т.к. в публичном, в сеттере хранится некая логика проверки.
                                //в то время как я хочу изменить вкладку без проверки
//Вызываю PropertyChanged("SelectedTabVM");

一切正常时选项卡会更改:

实施SelectedTabVM:

private Abstract VM _selectedTabVM;
public AbstractVM SelectedTabVM
    {
        get { return _selectedTabVM; }
        set
        {
            if (modeChangeExecute())
            {
                _selectedTabVM = value;
                NotifyPropertyChanged("SelectedTabVM");
            }                 
        }
    }

作品

但是当你通过界面切换标签时,你会得到这样的画面:

不起作用

那些。两个选项卡的属性IsSelected设置为True。有什么问题?IsSelected为什么属性没有从我从代码切换到的选项卡中删除

UPD 在英语 stackoverflow 上发现了一个不太相似的问题: https ://stackoverflow.com/questions/7929646/how-to-programmaticaly-select-a-tabitem-in-wpf-tabcontrol

这里看起来像是控制错误TabControl本身WPF

c#
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Best Answer
    VladD
    2020-09-27T22:02:02Z2020-09-27T22:02:02Z

    问题是您正在使用对话框阻止设置器。在对话期间,x 属性必须有一些值,因此 systemBinding的被关闭并且无法按预期工作。解决方案是从设置器中删除允许选项卡更改的逻辑。同时,二传手也将变得普遍适用。那么,UI 逻辑(消息框)可以从 VM 中卸载,这也不错。


    所以看看你需要什么。

    首先,我们从 setter 中删除长操作。二传手必须快。

    class OuterVM : INotifyPropertyChanged
    {
        // ...
        private VM _selectedTabVM;
        public VM SelectedTabVM
        {
            get { return _selectedTabVM; }
            set
            {
                if (_selectedTabVM == value) return;
                _selectedTabVM = value;
                NotifyPropertyChanged(nameof(SelectedTabVM));
            }
        }
    

    现在,我们需要禁用TabItem'a 的正常点击,并将其重定向到我们的代码。为此,您需要某种可以取消事件的 EventTrigger。(例如,像这里一样。)但是这种技术将传送EventArgs到它们不属于的 VM,所以让我们来看看附加的行为。(是的,这是一个严肃的武器,我没有找到另一个。)

    首先,通过 nuget 连接System.Windows.Interactivity.WPF(参考 → 鼠标右键 → Manage NuGet Packages... → Search = System.Windows.Interactivity.WPF)。让我们写Behavior:

    class RouteClickBehaviour : Behavior<TabItem>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.PreviewMouseDown += OnTabItemMouseDown;
        }
    
        protected override void OnDetaching()
        {
            AssociatedObject.PreviewMouseDown -= OnTabItemMouseDown;
            base.OnDetaching();
        }
    
        void OnTabItemMouseDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            // что делать тут?
        }
    }
    

    这里一切都很简单:连接时,我们订阅PreviewMouseDowny'a TabItem,断开连接时,我们取消订阅,当点击到达时,我们通过取消标准处理e.Handled。到底为什么PreviewMouseDown?因为这个事件在内部处理程序之前到达我们这里,我们可以在不让它进入内部的情况下取消它。

    现在问题来了,当检测到点击时怎么办?好的,您需要从 VM 调用命令,让 VM 决定下一步要做什么。从哪里获得命令和参数?答案很明显——通过附加属性附加。这些附加属性可以放在一个单独的类中,但您可以将它们放在RouteClickBehaviour.

    我们得到一个改进的版本:

    class RouteClickBehaviour : Behavior<TabItem>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.PreviewMouseDown += OnTabItemMouseDown;
        }
    
        protected override void OnDetaching()
        {
            AssociatedObject.PreviewMouseDown -= OnTabItemMouseDown;
            base.OnDetaching();
        }
    
        void OnTabItemMouseDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            var command = GetClickCommand(AssociatedObject);
            var commandParameter = GetClickCommandParameter(AssociatedObject);
            if (command == null)
                return;
            Dispatcher.CurrentDispatcher.InvokeAsync(() => command.Execute(commandParameter));
        }
    
        #region attached property ICommand ClickCommand
        public static ICommand GetClickCommand(DependencyObject obj) =>
            (ICommand)obj.GetValue(ClickCommandProperty);
        public static void SetClickCommand(DependencyObject obj, ICommand value) =>
            obj.SetValue(ClickCommandProperty, value);
        public static readonly DependencyProperty ClickCommandProperty =
            DependencyProperty.RegisterAttached(
                "ClickCommand", typeof(ICommand), typeof(RouteClickBehaviour));
        #endregion
    
        #region attached property object ClickCommandParameter
        public static object GetClickCommandParameter(DependencyObject obj) =>
            obj.GetValue(ClickCommandParameterProperty);
        public static void SetClickParameterCommand(DependencyObject obj, object value) =>
            obj.SetValue(ClickCommandParameterProperty, value);
        public static readonly DependencyProperty ClickCommandParameterProperty =
            DependencyProperty.RegisterAttached(
                "ClickCommandParameter", typeof(object), typeof(RouteClickBehaviour));
        #endregion
    }
    

    唯一的微妙之处在于我们异步发送命令。

    下一个问题是如何通过样式将附加行为添加到TabItem?如果我们TabItem手动创建它,就不会有问题:

    <TabItem Header="{Binding Header}" Width="100">
        <i:Interaction.Behaviors>
            <local:RouteClickBehaviour/>
        </i:Interaction.Behaviors>
    

    (并且命令可以通过参数传递)。但是我们有一种风格,通过一种风格传递行为,一切都变得复杂了。

    让我们使用标准解决方法:通过另一个附加属性。让我们添加RouteClickBehaviour这个:

        #region attached property bool Inject, calls OnInject on change
        public static bool GetInject(DependencyObject obj) =>
            (bool)obj.GetValue(InjectProperty);
        public static void SetInject(DependencyObject obj, bool value) =>
            obj.SetValue(InjectProperty, value);
        public static readonly DependencyProperty InjectProperty =
            DependencyProperty.RegisterAttached(
                "Inject", typeof(bool), typeof(RouteClickBehaviour),
                new PropertyMetadata(false, OnInject));
        #endregion
    
        static void OnInject(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            bool newValue = (bool)e.NewValue;
            var behaviours = Interaction.GetBehaviors(d);
            if (newValue)
            {
                behaviours.Add(new RouteClickBehaviour());
            }
            else
            {
                foreach (var b in behaviours.OfType<RouteClickBehaviour>().ToList())
                    behaviours.Remove(b);
            }
        }
    

    现在,如果您设置Inject = true,所需的行为将自动挂起。

    好的,接下来 - 我们需要在 VM 中使用一个命令来决定是否更改 Tab。对于命令,您可以使用普通的RelayCommand. 当然,我们将把命令放在它所在的同一个地方modeChangeExecute:

    class OuterVM : INotifyPropertyChanged
    {
        // ...
        public OuterVM()
        {
            ChangeRequested = new RelayCommand(o =>
            {
                if (modeChangeExecute())
                    SelectedTabVM = (VM)o;
            });
        }
    
        public ICommand ChangeRequested { get; }
    
        bool modeChangeExecute()
        {
            return MessageBox.Show("?", "?", MessageBoxButton.YesNo) == MessageBoxResult.Yes;
        }
    

    好的,VM 部分已准备就绪。现在让我们通过 XAML 将它们联系在一起。

    <TabControl ItemsSource="{Binding TabItemsVM}"
                SelectedItem="{Binding SelectedTabVM}"
                TabStripPlacement="Top"> 
                Name="TK">
        <TabControl.ItemContainerStyle>
            <Style TargetType="TabItem">
                <Setter Property="Header" Value="{Binding Header}"/>
                <Setter Property="Width" Value="100"/>
                <Setter Property="local:RouteClickBehaviour.Inject" Value="True"/>
                <Setter Property="local:RouteClickBehaviour.ClickCommand"
                        Value="{Binding DataContext.ChangeRequested, ElementName=TK}"/>
                <Setter Property="local:RouteClickBehaviour.ClickCommandParameter"
                        Value="{Binding}"/>
            </Style>
        </TabControl.ItemContainerStyle>
    </TabControl>
    

    设置local:RouteClickBehaviour.Inject = True为启用行为。该命令必须从TabControl'a 的 VM 获取,因为它是决定切换问题的外部 VM。作为参数,我们传递要激活的本地 VM。

    我们检查。应该管用。

    • 4
  2. iRumba
    2020-09-26T11:27:58Z2020-09-26T11:27:58Z

    为什么选项卡需要绑定属性IsSelected?限制自己SelectedItem,这就足够了。使用 IsSelected 属性,控件将在没有您的情况下解决。如果您需要知道 VM 中此属性的值,则通过以下方式绑定它Mode=OneWay

    更新:

    我就是这么做的。我必须马上说我并没有真正在意架构,我只是把它放进去这样它会更快:)

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:WpfApplication1"
            Title="MainWindow" Height="350" Width="525"
            DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
        <Grid>
            <TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}">
                <TabControl.ItemContainerStyle>
                    <Style TargetType="TabItem">
                        <Setter Property="Header" Value="{Binding Header}"/>
                        <Setter Property="Width" Value="100"/>
                        <Setter Property="Content" Value="{Binding Content}"/>
                    </Style>
                </TabControl.ItemContainerStyle>
            </TabControl>
        </Grid>
    </Window>
    

    这是背景

    public partial class MainWindow : Window
    {
        public ObservableCollection<Tab> Tabs { get; set; }
    
        public Tab SelectedTab { get; set; }
    
        public MainWindow()
        {
            Tabs = new ObservableCollection<Tab>
            {
                new Tab { Header="Tab1", Content="Tab1 content" },
                new Tab { Header="Tab2", Content="Tab2 content" },
                new Tab { Header="Tab3", Content="Tab3 content" }
            };
            SelectedTab = Tabs[0];
            InitializeComponent();
        }
    }
    
    public class Tab
    {
        public string Header { get; set; }
    
        public string Content { get; set; }
    }
    

    一切对我来说都很好,选项卡一一突出显示。你的问题很可能是在别处,你没有表现出来,以为与问题无关。查找分配给您的选项卡的位置IsSelected = true。或者完全从逻辑上抽象出来,只留下这个基础,其余的都装上stub,慢慢连接逻辑。

    • 1

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何停止编写糟糕的代码?

    • 3 个回答
  • Marko Smith

    onCreateView 方法重构

    • 1 个回答
  • Marko Smith

    通用还是非通用

    • 2 个回答
  • Marko Smith

    如何访问 jQuery 中的列

    • 1 个回答
  • Marko Smith

    *.tga 文件的组重命名(3620 个)

    • 1 个回答
  • Marko Smith

    内存分配列表C#

    • 1 个回答
  • Marko Smith

    常规赛适度贪婪

    • 1 个回答
  • Marko Smith

    如何制作自己的自动完成/自动更正?

    • 1 个回答
  • Marko Smith

    选择斐波那契数列

    • 2 个回答
  • Marko Smith

    所有 API 版本中的通用权限代码

    • 2 个回答
  • Martin Hope
    jfs *(星号)和 ** 双星号在 Python 中是什么意思? 2020-11-23 05:07:40 +0000 UTC
  • Martin Hope
    hwak 哪个孩子调用了父母的静态方法?还是不可能完成的任务? 2020-11-18 16:30:55 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    Arch ArrayList 与 LinkedList 的区别? 2020-09-20 02:42:49 +0000 UTC
  • Martin Hope
    iluxa1810 哪个更正确使用:if () 或 try-catch? 2020-08-23 18:56:13 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5