RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1090362
Accepted
A B
A B
Asked:2020-03-04 02:35:47 +0000 UTC2020-03-04 02:35:47 +0000 UTC 2020-03-04 02:35:47 +0000 UTC

如何防止 TabControl 中的索引更改

  • 772

我想在 WPF C# 应用程序中实现关闭未聚焦的 TabControl。

创建选项卡时,会为该选项卡标题中的关闭按钮分配一个索引,但事实是,当您删除选项卡时,例如在中间,选项卡本身的索引顺序会发生变化。因此,按钮标签将与此选项卡的索引不匹配。

为了更清楚,这里有一个例子: Button tag |0 1 2 3 4 5 Index on|0 1 2 3 4 5

关闭,例如,第三个选项卡:

按钮标签 |0 1 2 4 5 索引打开|0 1 2 3 4

存在不匹配。有些公式没用。

这是代码:

        public void New_TabControl(string n, Page p)
    {

        TabItem ti = new TabItem();
        DockPanel dp = new DockPanel();
        Label l = new Label();
        Button b = new Button();
        Frame f = new Frame();

        ti.MaxHeight = 50;
        ti.MaxWidth = 150;

        l.Content = n;
        l.MaxWidth = 100;

        b.Content = "x";
        b.Width = 30;
        b.Click += new RoutedEventHandler(Close_Tab);

        ti.Header = dp;

        ti.Content = f;
        f.Content = p;

        dp.Children.Add(l);
        dp.Children.Add(b);

        TB.Items.Add(ti);
        ti.Focus();
        b.Tag = this.TB.SelectedIndex;
    }

    public void Close_Tab(object sender, RoutedEventArgs e)
    {
        Button b = sender as Button;
        MessageBox.Show(b.Tag.ToString() + TB.SelectedIndex.ToString());
        TB.Items.RemoveAt(Convert.ToInt32(b.Tag));
    }

是否可以防止标签索引更改?如何正确实施?或者您是否需要做一些诸如存储当前选项卡和焦点标签的表格之类的事情?

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    EvgeniyZ
    2020-03-04T05:30:51Z2020-03-04T05:30:51Z

    这不太可能回答您的问题,因为我正在展示它应该如何以及您应该遵循的方向。

    因此,对于初学者,让我们看一下您的代码并写出一些问题区域:

    1. 您将数据存储在他们不需要知道的控件中。控件是视图,即与客户端交互的内容。他们需要知道什么才能成功?好吧,可能是颜色、大小、位置、样式以及与显示相关的所有内容。您不应该存储在控件中的所有其他内容。比如你Tag存储了一个索引,控件为什么要知道这个?

    2. 您通过代码使用控件。这在某种意义上是可以接受的,但这样做是在做很多不应该打扰你的不必要的工作。试想一下,您根本没有界面,或者一段时间后您决定重写您的项目并使其成为一个控制台应用程序,然后呢?你会创建一个新项目,在那里复制逻辑,重写它以使用控制台吗?如果我们最初从接口中解开这个逻辑,使其具有通用性,那么我们就不必重写项目,因为连接一个新接口就足够了,仅此而已。

    3. Frame- 他自己有很多多余的东西,不允许做太多事情。例如尝试通过子框架改变主框架的内容。你可以而且一定会成功,但难度很大。

    基于此,我强烈建议您至少使用绑定开始开发项目,因为这是 WPF 的主要“武器”,没有它们,您的项目会损失很多,包括工作速度。


    现在让我们编写一个小的 MVVM 项目,它将具有以下内容:

    • 带有标签的主页。
    • 能够关闭标签。
    • 能够设置所需选项卡的内容。

    首先,让我们定义什么是 MVVM:
    简而言之,这是一种将您的代码划分为特定层的方法,其中每一层响应一个事物:

    • 模型- 数据源。假设您有一个网站,该网站具有用于与其数据交互的 API 方法。这是使用这个 API 的工作,接收数据,发送数据,这就是所有的模型。
    • 查看- 这是用户界面。该层仅负责渲染。我还想指出,这一层不应该对其他层一无所知。
    • ViewModel - 一种生成数据以供显示的连接层。例如,模型层包含一个人的姓名、他的 id 和年龄。如果这本质上是内部信息(例如),为什么我们需要给用户一个 ID?所以我们制作了用户的 ViewModel,只有他的名字和年龄,我们制作的方法可以帮助我们正确显示它,就是这样,剩下的逻辑在 Model 层。

    现在我们可以开始开发项目了:

    • 为方便起见,让我们创建一个文件夹ViewModel
    • 在创建的文件夹中,我们将创建一个类MainViewModel- 这将是我们的主类,我们将绑定 View 层。
    • 接下来,转到App.xaml并删除那里的行StartupUri="MainWindow.xaml"
    • 我们进入App.xaml.cs并覆盖OnStartup我们初始化MainViewModel所需窗口的方法:

      protected override void OnStartup(StartupEventArgs e)
      {
          base.OnStartup(e);
          new MainWindow { DataContext = new MainViewModel() }.Show();
      }
      

      你问“这是为什么?” 对于这个问题,我会留给你这个链接。

    • 让我们创建一个类TabViewModel- 这将是一个选项卡的 ViewModel。
    • 在这个类中,我们将创建必要的属性(请记住,绑定只能绑定到公共属性!)。我们需要什么?选项卡的标题和可能的内容,为了方便,我们还重新定义了构造函数:

      class TabViewModel
      {
          public TabViewModel(string title, object content = default)
              => (Title, Content) = (title, content);
      
          public string Title { get; set; }
          public object Content { get; set; }
      }
      
    • 现在我们需要在主 VM 中创建这些选项卡的集合。在这里您应该立即考虑我们将来是否会更新选项卡?如果是,那么我们需要一个具有实现接口的集合INotifyCollectionChanged,没有它你将看不到视图层的变化。在现成的解决方案中,有ObservableCollection<T>或BindingList<T>。如果更改不重要,那么您可以使用任何内容。

      class MainViewModel
      {
          public ObservableCollection<TabViewModel> Tabs { get; } = new ObservableCollection<TabViewModel>
          {
              new TabViewModel("Вкладка 1"),
              new TabViewModel("Вкладка 2"),
              new TabViewModel("Вкладка 3")
          };
      }
      
    • 好吧,我们仍然需要将这一切联系起来。我们制作所需类型的选项卡并将它们绑定到集合:

      <TabControl ItemsSource="{Binding Tabs}">
          <TabControl.ItemTemplate>
              <DataTemplate>
                  <TextBlock Text="{Binding Title}"/>
              </DataTemplate>
          </TabControl.ItemTemplate>
          <TabControl.ContentTemplate>
              <DataTemplate>
                  <ContentPresenter Content="{Binding Content}" />
              </DataTemplate>
          </TabControl.ContentTemplate>
      </TabControl>
      

    就是这样,我们启动项目并看到集合中的 3 个选项卡,其中包含空内容:

    结果1

    请注意,所有选项卡式代码都可以在没有界面的情况下成功运行,我们不使用框架,我们已将所有内容放置在其位置。

    现在让我们在标签中显示不同的内容:
    我们将按照这个例子来做。

    • 让我们在目录中创建一个新类ViewModel,我们称之为 example FirstPageViewModel,让这个类只包含 1 个属性,它的文本:

      class FirstPageViewModel
      {
          public string Text { get; } = "Первая страница";
      }
      
    • 接下来,让我们创建一个新目录,我们将其命名为View.

    • 在这个目录中,我们将添加一个新的“用户控件”,我们将简单地调用它FirstPage。我们为这个页面提供了所需的设计,我还将显示 VM 层中包含的文本:

      <Grid>
          <Grid.Background>
              <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                  <GradientStop Color="White" Offset="0"/>
                  <GradientStop Color="Gainsboro" Offset="1"/>
              </LinearGradientBrush>
          </Grid.Background>
          <TextBlock Text="{Binding Text}" 
                     FontSize="30"
                     VerticalAlignment="Center" HorizontalAlignment="Center"/>
      </Grid>
      
    • 现在我们需要将它们全部组合起来,让我们在主窗口中进行:

      • 在最顶部添加指向 VM 和 V 目录的链接(这里的命名空间应该是你的,我举个例子来说明应该是什么):

        xmlns:vm="clr-namespace:WPFApp.ViewModel"
        xmlns:v="clr-namespace:WPFApp.View"
        
      • 我们添加一点TabControl,给它添加资源(其实它们可以在任何地方,但是如果我们只需要这些页面来做这个控件,那么我们就给它设置资源):

        <TabControl.Resources>
            <DataTemplate DataType="{x:Type vm:FirstPageViewModel}">
                <v:FirstPage/>
            </DataTemplate>
        </TabControl.Resources>
        

        这里一切都很简单,我们DataType在帮助下指定对象的类型,并在里面DataTemplate为指定类型设置所需的视图。

    • 好吧,现在让我们设置,例如,具有所需内容的第一个选项卡(请记住,我们TabViewModel为类创建了一个包含内容的属性):

      new TabViewModel("Вкладка 1", new FirstPageViewModel()),
      

    我们启动并看到第一个选项卡已更改其外观:

    结果2

    所以我们有一个选项卡式应用程序,每个选项卡都可以有自己的内容,没有框架!

    最后,关闭选项卡:

    什么是闭包?这本质上是从列表中删除一个对象。但是,如果根据 MVVM 的规则,我们不能订阅按钮事件和访问控制,我们怎么能从集合中移除一个对象呢?一切都很简单,这里的事件和命令来救援!

    • 让我们创建一个为我们实现接口的类ICommand。网上有很多实现资料,我就拿这一份,因为我们只需要方便地处理命令,不多说了:

      public class RelayCommand : ICommand
      {
          private Action action;
          public RelayCommand(Action action) => this.action = action;
          public bool CanExecute(object parameter) => true;
          #pragma warning disable CS0067
          public event EventHandler CanExecuteChanged;
          #pragma warning restore CS0067
          public void Execute(object parameter) => action();
      }
      
    • 添加TabViewModel一个新命令,该命令将通知所有订阅者该选项卡正在关闭:

      class TabViewModel
      {
          public TabViewModel(string title, object content = default)
          {
              (Title, Content) = (title, content);
              CloseCommand = new RelayCommand(Close);
          }
      
          public event Action<TabViewModel> OnClose;
          public ICommand CloseCommand { get; }
          public string Title { get; set; }
          public object Content { get; set; }
          void Close() => OnClose?.Invoke(this);
      }
      

      看看这里发生了什么。当单击某个绑定到 的按钮(稍后会详细介绍)时,将CloseCommand调用Close()该方法,该方法反过来通知所有订阅者OnClose该选项卡需要关闭的事件。

    • 现在我们有两条路径。1. 这是您初始化TabViewModel自己以进行订阅的时候。2. 我们可以稍微自动化一下。让我们做第二个。让我们重写一点MainViewModel(或者更确切地说,初始化选项卡的集合):

      class MainViewModel
      {
          public MainViewModel()
          {
      
              Tabs.CollectionChanged += OnTabsChanged;
              Tabs.Add(new TabViewModel("Вкладка 1", new FirstPageViewModel()));
              Tabs.Add(new TabViewModel("Вкладка 2"));
              Tabs.Add(new TabViewModel("Вкладка 3"));
          }
      
          public ObservableCollection<TabViewModel> Tabs { get; } = new ObservableCollection<TabViewModel>();
      
          private void OnTabsChanged(object sender, NotifyCollectionChangedEventArgs e)
          {
              if (e.OldItems != null)
              {
                  foreach (TabViewModel item in e.OldItems)
                      item.OnClose -= CloseTab;
              }
              if (e.NewItems != null)
              {
                  foreach (TabViewModel item in e.NewItems)
                      item.OnClose += CloseTab;
              }
          }
      
          void CloseTab(TabViewModel tab) => Tabs.Remove(tab);
      }
      

      这里我们将选项卡的添加移到构造函数中,并且还添加了订阅CollectionChanged该集合的事件。此事件发生在其中添加/删除某些内容的那一刻。添加新标签时,我们订阅它的关闭事件,而删除它时,相反,我们取消订阅。我在这里
      写过这种方法。

    • 我们仍然需要在 View 层中创建一个关闭按钮,将其添加到TabControl.ItemTemplate:

      <TabControl.ItemTemplate>
          <DataTemplate>
              <StackPanel Orientation="Horizontal">
                  <TextBlock Text="{Binding Title}"/>
                  <Button Content="x" Command="{Binding CloseCommand}" 
                          Background="Transparent" BorderThickness="0"
                          Margin="3 -2 0 0"
                          Cursor="Hand"/>
              </StackPanel>
          </DataTemplate>
      </TabControl.ItemTemplate>
      

      这里起到了主要作用Command,里面有一个对created属性的绑定TabViewModel。

    一切,我们启动并享受结果:

    结果3

    几个补充:

    • 如果您的属性可以在应用程序运行时更改(例如,选项卡的内容),那么不要忘记实现INotifyPropertyChanged,因为没有它您将不会在界面中获得更改。
    • 如果您需要以编程方式打开所需的选项卡,请在包含当前打开的选项卡的集合旁边创建一个属性(使用 INPC)TabViewModel并将其绑定到TabControl(注意:) SelectedItem="{Binding SelectedItemProperty}"。
    • MVVM 是一种规则,可以使您的项目更加高效和方便。遵循这种方法,这取决于你。

    总的来说,祝你学习 C# 好运!

    • 4

相关问题

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

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

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +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
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +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