目录

介绍

背景

使用代码

TabWindow库的细分

带有关闭按钮的自定义TabItem

派生的TabControl支持自定义选项卡之间的拖放

允许将一个窗口切换到另一个窗口的TabWindow

总结


  • 下载源代码35 KB

介绍

本文介绍了一个称为TabWindow的Shell窗口,它嵌入了TabControl,允许通过拖放将选项卡项分离到一个新窗口。它还允许通过拖放将浮动窗口选项卡固定到窗口中。

背景

您能想象WPF窗口的行为类似于Chrome或Internet Explorer浏览器吗?在运行时,可以通过拖放将一个窗口选项卡移动到另一个窗口中。选项卡可以重新排序,单个选项卡可以关闭。TabWindow支持这些功能。但是,它不仅仅是Chrome等现代浏览器的复制品。有一些主要区别。例如,当TabWindow中选项卡仅剩一项时,选项卡页头就会消失。在GUI中,空间是一项额外的功能,如您所知。同样,当您将一个窗口选项卡移动到另一个窗口时,也可以将其拖到标题栏,而不是由Chrome浏览器拖到选项卡标题的位置。TabWindow,但是,不是一个docking控制。已经有许多商业和开源docking控件可用。TabWindow派生自WPF Window 类,因此所有窗口特性都由开发人员公开。

使用代码

在您的代码中使用TabWindow很简单。在将对TabWindow库的引用添加到您的项目后,首先将TabWindow实例化为常规WPF窗口的引用,然后通过传递将成为TabWindow实例内容的Control实例来调用AddTabItem方法。因此,构建您自己的漂亮用户控件,然后将其传递给TabWindow。

TabWindow.TabWindow tabWin = new TabWindow.TabWindow();
TextBox tb = new TextBox();
tb.Text = "Test Demo";
tabWin.AddTabItem(tb.Text, tb);
tabWin.Show();

根据您的需要,创建尽可能多的TabWindow,然后通过将一个窗口拖到另一个窗口上来开始选项卡窗口。当一个窗口被拖动进入固定TabWindow对象的边界时,将出现一个选项卡放置目标图像。继续拖动,直到鼠标指针悬停在选项卡放置图像上方,然后放开鼠标。拖动的窗口消失,固定的窗口将添加一个新选项卡,其中包含拖动的窗口的内容。

1、两个独立悬浮的TabWindows。

2、将“Test 0”窗口拖到“Test Demo”窗口上。

3、选项卡区域在“测试演示”窗口中突出显示。释放按下的鼠标按钮,然后将“Test 0”窗口切换到“Test Demo”窗口。

为了将选项卡分离到新窗口,请按住选项卡标题并将其拖出现有窗口,或双击选项卡标题。它将创建一个独立的窗口。

TabWindow库的细分

库中主要包括三个部分。每个部分负责其自身的功能。

  • 使用关闭按钮的自定义TabItem
  • 派生自支持自定义TabItem拖放的TabControl
  • 允许将一个窗口切换到另一个窗口的TabWindow

带有关闭按钮的自定义TabItem

根据Internet上的快速搜索,有多种方法可以完成此任务。我采用了一种方法来创建一个衍生自TabItem的自定义控件。为了在选项卡标题上绘制[x]标记,控件模板样式在XAML中声明。最初,我想到了在选择选项卡时使用图像文件显示[x]标记,但最终使用System.Windows.Shapes.Path对象绘制x形状。这是在Generic.xaml定义[x]按钮的方式。

<ControlTemplate TargetType="{x:Type Button}"><Border x:Name="buttonBorder" CornerRadius="2" Background="{TemplateBinding Background}" BorderBrush="DarkGray" BorderThickness="1"><Path x:Name="buttonPath" Margin="2" Stroke="DarkGray" StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" Stretch="Fill" ><Path.Data><PathGeometry><PathFigure StartPoint="0,0"><LineSegment Point="13,13"/></PathFigure><PathFigure StartPoint="0,13"><LineSegment Point="13,0"/></PathFigure></PathGeometry></Path.Data></Path></Border><ControlTemplate.Triggers>...</ControlTemplate.Triggers>
</ControlTemplate>

如下所示,此关闭按钮样式将应用于选项卡标题模板。DockPanel由停靠在最右侧的[X]和标头ContentPresenter组成。[x]按钮的默认可见性被隐藏。选择选项卡后,它变为可见。用于Trigger显示或隐藏[x]按钮。

<Style TargetType="{x:Type local:CloseEnabledTabItem}">
...<Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type local:CloseEnabledTabItem}"><Grid SnapsToDevicePixels="true" IsHitTestVisible="True" x:Name="gridHeader"><Border x:Name="tabItemBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,1,1,0" ><DockPanel x:Name="tabItemDockPanel"><Button x:Name="tabItemCloseButton" Style="{StaticResource tabItemCloseButtonStyle}" DockPanel.Dock="Right" Margin="3,0,3,0" Visibility="Hidden" /><ContentPresenter x:Name="tabItemContent" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" RecognizesAccessKey="True" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" ContentSource="Header" Margin="{TemplateBinding Padding}"/></DockPanel></Border></Grid>...</ControlTemplate></Setter.Value></Setter>
</Style>

现在我们需要进行一些操作。我希望在单击[x]按钮时将选项卡项删除。双击选项卡标题时,我也想引发一个事件。此双击通知将由TabWindow使用,在那里它将生成一个新 TabWindow并将内容从单击的选项卡项移动到新窗口。基本上,这等效于将选项卡拖出到新窗口,因此双击选项卡标题可创建一个新TabWindow实例,并删除双击的选项卡项。

public override void OnApplyTemplate()
{base.OnApplyTemplate();Button closeButton = base.GetTemplateChild("tabItemCloseButton") as Button;if (closeButton != null)closeButton.Click += new System.Windows.RoutedEventHandler(closeButton_Click);Grid headerGrid = base.GetTemplateChild("gridHeader") as Grid;if (headerGrid != null)headerGrid.MouseLeftButtonDown += new MouseButtonEventHandler(headerGrid_MouseLeftButtonDown);
}void closeButton_Click(object sender, System.Windows.RoutedEventArgs e)
{var tabCtrl = this.Parent as TabControl;if (tabCtrl != null)tabCtrl.Items.Remove(this);
}void headerGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{if (e.ClickCount == 2)this.RaiseEvent(new RoutedEventArgs(TabHeaderDoubleClickEvent, this));
}

派生的TabControl支持自定义选项卡之间的拖放

网上有很多拖放教程,因此我将不详细介绍通过拖放对选项卡进行重新排序。但是,将选项卡拖出以创建新窗口不是典型的拖放操作。.NET Framework提供了在拖动鼠标指针期间连续引发的QueryCotinueDrag事件。拖动的鼠标位置保持选中状态,当它离开选项卡控件边界时,它会创建一个新的TabWindow。一旦新的TabWindow被创建,新窗口的Left和Top属性通过处理QueryContinueDrag事件得到更新。当放置操作发生时,此事件还提供信号。由于e.KeyStates被设定为DragDropKeyStates.None,是时候从选项卡控件中删除选项卡项。

void DragSupportTabControl_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{if (e.KeyStates == DragDropKeyStates.LeftMouseButton){Win32Helper.Win32Point p = new Win32Helper.Win32Point();if (Win32Helper.GetCursorPos(ref p)){Point _tabPos = this.PointToScreen(new Point(0, 0));if (!((p.X >= _tabPos.X && p.X <= (_tabPos.X + this.ActualWidth) && p.Y >= _tabPos.Y && p.Y <= (_tabPos.Y + this.ActualHeight)))){var item = e.Source as TabItem;if (item != null)UpdateWindowLocation(p.X - 50, p.Y - 10, item);}else{if (this._dragTornWin != null)UpdateWindowLocation(p.X - 50, p.Y - 10, null);}}}else if (e.KeyStates == DragDropKeyStates.None){this.QueryContinueDrag -= DragSupportTabControl_QueryContinueDrag;e.Handled = true;if (this._dragTornWin != null){_dragTornWin = null;var item = e.Source as TabItem;if (item != null)this.RemoveTabItem(item);}}
}

不幸的是,WPF没有提供可靠的方法来检索桌面屏幕上的当前鼠标位置。如果鼠标指针位于Control中,则有一种可靠的方法来获取准确的鼠标位置,但是将鼠标指针拖出控件或窗口时并非如此。无论鼠标指针在控件内还是窗口外,检索鼠标位置对我来说都是至关重要的。我的帮助来自Win32 API。

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);

允许将一个窗口切换到另一个窗口的TabWindow

允许将一个窗口拖放到另一个窗口上以进行选项卡显示是一项艰巨的任务。首先,如果通过窗口标题栏拖动窗口,则不会引发任何拖放事件。我必须使用HwndSource类来处理必要的窗口消息。在SourceInitialized事件处理程序中(在TabWindow创建之后),获取当前窗口实例的HwndSource,然后调用AddHook以包括在窗口过程链中。

void TabWindow_SourceInitialized(object sender, EventArgs e)
{HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);source.AddHook(new HwndSourceHook(WndProc));
}

因此,当窗口被标题栏抓住并拖动时,Win32消息将在钩子处理程序中接收。我们仅处理与目标相关的窗口消息。我们的目标是什么?我想在TabWindow标题栏开始拖动时得到通知。那是WM_ENTERSIZEMOVE信息。在TabWindow拖动这些对象时,需要处理窗口的坐标,这就是WM_MOVE消息。最后,WM_EXITSIZEMOVE指示拖动完成。处理这些winProc消息可以实现我们的目标。将一个 TabWindow拖到另一个TabWindow上时,将显示选项卡放置区图像。将拖动的窗口拖放到选项卡放置区域图像上,拖动的窗口将成功添加到固定窗口。

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{if (msg == Win32Helper.WM_ENTERSIZEMOVE)_hasFocus = true;else if (msg == Win32Helper.WM_EXITSIZEMOVE){_hasFocus = false;DragWindowManager.Instance.DragEnd(this);}else if (msg == Win32Helper.WM_MOVE){if (_hasFocus)DragWindowManager.Instance.DragMove(this);}handled = false;return IntPtr.Zero;
}

拖动的TabWindow如何判断下面的窗口是否为TabWindow类型?好了,当TabWindow实例化时,它会将自己注册到DragWindowManger单例实例。每当TabWindow移动时,它就会循环访问所有注册的窗口,以检测所拖动的鼠标位置是否在这些TabWindow实例之一上。

public void DragMove(IDragDropToTabWindow dragWin)
{if (dragWin == null) return;Win32Helper.Win32Point p = new Win32Helper.Win32Point();if (!Win32Helper.GetCursorPos(ref p)) return;Point dragWinPosition = new Point(p.X, p.Y);foreach (IDragDropToTabWindow existWin in _allWindows){if (dragWin.Equals(existWin)) continue;if (existWin.IsDragMouseOver(dragWinPosition)){if (!_dragEnteredWindows.Contains(existWin))_dragEnteredWindows.Add(existWin);}else{if (_dragEnteredWindows.Contains(existWin)){_dragEnteredWindows.Remove(existWin);existWin.OnDrageLeave();}}}
...
}

将拖动的TabWindow放到选项卡放置区域后,拖动窗口的内容将传输到在目标TabWindow上创建的新选项卡。然后拖拽的TabWindow消失了。

public void DragEnd(IDragDropToTabWindow dragWin)
{if (dragWin == null) return;Win32Helper.Win32Point p = new Win32Helper.Win32Point();if (!Win32Helper.GetCursorPos(ref p)) return;Point dragWinPosition = new Point(p.X, p.Y);foreach (IDragDropToTabWindow targetWin in _dragEnteredWindows){if (targetWin.IsDragMouseOverTabZone(dragWinPosition)){System.Windows.Controls.ItemCollection items = ((ITabWindow)dragWin).TabItems;for (int i = 0; i < items.Count; i++){System.Windows.Controls.TabItem item = items[i] as System.Windows.Controls.TabItem;if (item != null)((ITabWindow)targetWin).AddTabItem(item.Header.ToString(), (System.Windows.Controls.Control)item.Content);}for (int i = items.Count; i > 0; i--){System.Windows.Controls.TabItem item = items[i - 1] as System.Windows.Controls.TabItem;if (item != null)((ITabWindow)dragWin).RemoveTabItem(item);}}targetWin.OnDrageLeave();}if (_dragEnteredWindows.Count > 0 && ((ITabWindow)dragWin).TabItems.Count == 0){((Window)dragWin).Close();}_dragEnteredWindows.Clear();
}

总结

TabWindow库在复合应用程序中非常有用,在复合应用程序中,模块可以直接加载到TabWindow实例中。然后,将如何将窗口动态合并到选项卡的决定权留给用户。TabWindow的亮点是:

  • 允许对选项卡项进行重新排序
  • 允许关闭选项卡项
  • 当窗口中仅剩一个选项卡项时,选项卡标题将不可见
  • 可以将选项卡项拖出到新窗口
  • 双击选项卡标题会创建一个新窗口
  • 可以通过标题栏拖动一个窗口并将其放在另一个窗口上。源窗口的内容成为目标窗口的新选项卡项。

WPF窗口允许通过拖放进行切换相关推荐

  1. 为 Revit API 插件创建 WPF 窗口的基本项目(Revit API+WPF 系列 2/3)

    在本系列的第二部分中,我们将讨论在创建 WPF 窗口时应用的基本项目. 为了继续,请确保您的插件项目能够创建 WPF 窗口.如果没有,您最好创建一个具有 WPF 功能的项目模板并创建另一个新项目.然后 ...

  2. WPF窗口长时间无人操作鼠标自动隐藏

    原文:WPF窗口长时间无人操作鼠标自动隐藏 在软件开发中有时会有等待一段时间无人操作后隐藏鼠标,可能原因大致如下: 1.为了安全性,特别是那些需要用到用户名和密码登录服务端的程序,常常考虑长期无人操作 ...

  3. WPF 窗口居中 变更触发机制

    原文:WPF 窗口居中 & 变更触发机制 窗口居中 & 变更触发机制 解决: 1.单实例窗口,窗口每次隐藏后再显示时,位置居中显示 2.多屏幕下单实例窗口,当父窗口移动到其它屏幕时,单 ...

  4. 设置WPF窗口相对于非WPF窗口的位置

    在前一个Post当中,指出了在WPF的WindowInteropHelper类中的一个BUG:通过WindowInteropHelper的Owner属性不能实现把WPF窗口的Owner属性设置为一个非 ...

  5. [WPF疑难]如何禁用WPF窗口的系统菜单(SystemMenu)

    [WPF疑难]如何禁用WPF窗口的系统菜单(SystemMenu) 原文 [WPF疑难]如何禁用WPF窗口的系统菜单(SystemMenu) [WPF疑难]如何禁用WPF窗口的系统菜单(SystemM ...

  6. WPF 窗口最大化正确方法

    WPF 窗口最大化时,会挡住任务栏. 设置 this.MaxHeight = SystemParameters.PrimaryScreenHeight; 再赋值 WindowState 最大化就不会挡 ...

  7. Android视频播放器实现小窗口和全屏状态切换

    Android视频播放器实现小窗口和全屏状态切换 实在是不好意思,楼下评论的兄弟久等了,这文章一直没写第一是没时间,第二是自己准备也不充足,最近才看了好几个Android视频播放器的开源项目,才对视频 ...

  8. Unity Scene窗口 平行网格/透视网格 切换

    Unity Scene窗口 平行网格/透视网格 切换 之前在网上找了半天,解答都是摄像机的正交与透视切换. 而我的问题是这样的! 现在没有立体感,我想要变回的效果是这样的! 最后终于找到了变换视图的方 ...

  9. WPF 在同一窗口区域实现多界面切换

    实现类似 C# TabControl 控件的效果,在同一区域实现界面的切换.关键词:ContentControl , Frame , Page 实现步骤: 1 在界面布局的预定区域放置一个Conten ...

最新文章

  1. 怎么自学python编程-零基础如何自学编程?
  2. 解决text-overflow: ellipsis;不生效的问题
  3. 亚马逊高级应用科学家熊元骏:人类行为理解研究进展 | 直播实录·PhD Talk
  4. Requested resource=<memory:-1, vCores:1>问题解决
  5. 吴军:站在浪潮之巅,5G 和 IoT 才是未来 10 年的浪潮 | 人物志
  6. 浅析 Spring 中的事件驱动机制
  7. 以写代学:python 元组
  8. 遗传算法 python_Python实现入门级遗传算法
  9. 软考《软件设计师教程》(第五版)
  10. 电脑编程就业找哪方面
  11. linux中配置网桥的命令是下列哪一项,linux系统下配置网桥
  12. 全国哀悼日,英来网停站一天。
  13. 飞秋官方下载 找了很久的
  14. 关于如何解释机器学习的一些方法
  15. 化合物分子 ogb、dgl生成图网络及GNN模型训练;pgl图框架
  16. 祝读者朋友们新年快乐
  17. 电脑常识某企业桌面虚拟化项目-Citrix虚拟桌面解决方案
  18. ppt转换成pdf转换器下载
  19. 数据处理之Pandas中数据类型转换
  20. 蓝桥杯双阶乘 (python)

热门文章

  1. oracle运行产生的日志在哪里,运行临时表,是否有日志产生
  2. 电脑字体在哪个文件夹_在PS里如何安装字体
  3. 代码合并工具_合并上千个Excel表格,1秒就能完成
  4. 设计灵感|什么样的登录页能让用户感到体贴?
  5. 设计灵感|展览海报的设计没有头绪?
  6. 纯文字极简风格平面海报,PSD分层模板!
  7. UI设计干货素材|动效导航,漂亮的悬停动效
  8. mac mysql 的lb_简单Mysql的lb集群
  9. 怎么把照片做成消消乐_开心消消乐特效怎么制作 制造的几种方式分享
  10. Ubuntu的一些命令