赞
踩
效果图:
控件:支持左边点击,右边内容滚到顶部,右边鼠标中键滚动,左边菜单栏跟着变化。
1.控件样式代码App.xaml
- <Application
- x:Class="HT.ControlWPF.App"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:HT.ControlWPF"
- StartupUri="MainWindow.xaml">
- <Application.Resources>
- <Style TargetType="{x:Type local:CustomMenuScrollViewer}">
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type local:CustomMenuScrollViewer}">
- <Grid>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto" />
- <ColumnDefinition Width="*" />
- </Grid.ColumnDefinitions>
- <!-- 菜单栏 -->
- <ScrollViewer
- Grid.Row="0"
- Grid.Column="0"
- HorizontalScrollBarVisibility="Auto"
- VerticalScrollBarVisibility="Auto">
- <ItemsControl x:Name="PART_Mune">
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <StackPanel Orientation="Vertical" />
- </ItemsPanelTemplate>
- </ItemsControl.ItemsPanel>
- <ItemsControl.ItemTemplate>
- <DataTemplate>
- <Grid>
- <RadioButton
- MinWidth="120"
- HorizontalContentAlignment="Stretch"
- Command="{Binding CheckedCommand}"
- CommandParameter="{Binding ElementName=PART_Function_Content}"
- Content="{Binding Title}"
- Cursor="Hand"
- GroupName="rd"
- IsChecked="{Binding IsSeleted, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
- <RadioButton.Resources>
- <Style TargetType="{x:Type RadioButton}">
- <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
- <Setter Property="BorderThickness" Value="1" />
- <Setter Property="Padding" Value="10,5,10,5" />
- <Setter Property="Margin" Value="2,0,2,0" />
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type RadioButton}">
- <Border
- x:Name="content_Border"
- Margin="{TemplateBinding Margin}"
- Padding="{TemplateBinding Padding}">
- <ContentPresenter
- x:Name="contentPresenter"
- HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
- VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
- Focusable="False"
- RecognizesAccessKey="True"
- SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
- </Border>
- <ControlTemplate.Triggers>
- <Trigger Property="IsMouseOver" Value="True">
- <Setter Property="Foreground" Value="#379aff" />
- </Trigger>
- <DataTrigger Binding="{Binding IsSeleted}" Value="True">
- <Setter Property="Foreground" Value="#fff" />
- <Setter TargetName="content_Border" Property="Background" Value="#379aff" />
- <Setter TargetName="content_Border" Property="CornerRadius" Value="0 10 10 0" />
- </DataTrigger>
- </ControlTemplate.Triggers>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- </RadioButton.Resources>
- </RadioButton>
- </Grid>
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
- </ScrollViewer>
- <!-- 功能内容 -->
- <ScrollViewer
- x:Name="PART_Function_Content"
- Grid.Row="0"
- Grid.Column="1">
- <ScrollViewer.Resources>
- <Style TargetType="Label">
- <Setter Property="FontSize" Value="16" />
- <Setter Property="Margin" Value="5,2,0,2" />
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type Label}">
- <Grid Margin="{TemplateBinding Margin}">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto" />
- <ColumnDefinition Width="*" />
- </Grid.ColumnDefinitions>
- <Rectangle
- Width="4"
- Height="18"
- Margin="0,0,5,0"
- Fill="#379aff" />
- <ContentPresenter
- Grid.Column="1"
- HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
- VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
- RecognizesAccessKey="True"
- SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
- </Grid>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- <Style TargetType="{x:Type GroupBox}">
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type GroupBox}">
- <Grid SnapsToDevicePixels="true">
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <Border x:Name="Header" Grid.Row="0">
- <StackPanel>
- <ContentPresenter
- Name="header"
- ContentSource="Header"
- RecognizesAccessKey="True"
- SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
- Visibility="Collapsed" />
- <Label HorizontalAlignment="Left" Content="{Binding ElementName=header, Path=Content}" />
- <Border Height="1" Background="#eeeeee" />
- </StackPanel>
- </Border>
- <ContentPresenter
- Grid.Row="1"
- Margin="{TemplateBinding Padding}"
- SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
- </Grid>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- </ScrollViewer.Resources>
- </ScrollViewer>
- </Grid>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- </Application.Resources>
- </Application>
2.控件业务代码CustomMenuScrollViewer.cs
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Linq;
- using System.Windows;
- using System.Windows.Controls;
-
- namespace HT.ControlWPF
- {
- [TemplatePart(Name = PART_Function_Content, Type = typeof(ScrollViewer))]
- [TemplatePart(Name = PART_Mune, Type = typeof(StackPanel))]
- public class CustomMenuScrollViewer : Control
- {
- public const string PART_Function_Content = nameof(PART_Function_Content);
- public const string PART_Mune = nameof(PART_Mune);
- /// <summary>
- /// 菜单栏目录
- /// </summary>
- private List<MuneInfo> muneInfos = new List<MuneInfo>();
- /// <summary>
- /// 是否加载
- /// </summary>
- private bool isLoaded = false;
- /// <summary>
- /// 需要更新菜单
- /// </summary>
- private bool hasUpdateMune = true;
- /// <summary>
- /// 需要更新内容 为2则更新
- /// </summary>
- private int hasUpdateContentOffset = 0;
- /// <summary>
- /// 内容控件
- /// </summary>
- private ScrollViewer content_ScrollViewer = null;
- /// <summary>
- /// 菜单控件
- /// </summary>
- private ItemsControl mune = null;
- static CustomMenuScrollViewer()
- {
- DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomMenuScrollViewer), new FrameworkPropertyMetadata(typeof(CustomMenuScrollViewer)));
- }
- public override void OnApplyTemplate()
- {
- content_ScrollViewer = this.GetTemplateChild(PART_Function_Content) as ScrollViewer;
- mune = this.GetTemplateChild(PART_Mune) as ItemsControl;
- content_ScrollViewer.Content = this.Content;
- content_ScrollViewer.Loaded += ScrollViewer_Loaded;
- content_ScrollViewer.ScrollChanged += PART_Function_Content_ScrollChanged;
- base.OnApplyTemplate();
- }
- /// <summary>
- /// 内容绑定
- /// </summary>
- [Bindable(true)]
- public object Content
- {
- get { return GetValue(ContentProperty); }
- set { SetValue(ContentProperty, value); }
- }
- public static readonly DependencyProperty ContentProperty =DependencyProperty.Register(nameof(Content),typeof(object),typeof(CustomMenuScrollViewer),new FrameworkPropertyMetadata(null));
-
- private void RadioButton_Checked(MuneInfo selectedDataContext)
- {
- if (!hasUpdateMune) return;
- hasUpdateContentOffset = 0;
- content_ScrollViewer.ScrollToVerticalOffset(selectedDataContext.StartPoint);
- }
- private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
- {
- if (isLoaded) return;
- //找出有Name的GroupBox
- var group_boxs = ((Panel)this.content_ScrollViewer.Content).Children.OfType<GroupBox>().Where(s => !string.IsNullOrWhiteSpace(s.Name)).ToList();
- for (int i = 0; i < group_boxs.Count; i++)
- {
- var key = group_boxs[i].Name;
- var title = group_boxs[i].Header.ToString();
- //计算GroupBox在ScrollViewer的开始位置和结束位置
- double endPoint = 0;
- double startPoint = 0;
- for (int j = 0; j <= i; j++)
- {
- endPoint += group_boxs[j].ActualHeight;
- if (j == i) continue;
- startPoint += group_boxs[j].ActualHeight;
- }
- muneInfos.Add(new MuneInfo()
- {
- PageKey = key,
- IsSeleted = false,
- Title = title,
- StartPoint = startPoint,
- EndPoint = endPoint
- });
- }
- foreach (var item in muneInfos)
- {
- item.Checked += RadioButton_Checked;
- }
- mune.ItemsSource = muneInfos;
- }
- private void PART_Function_Content_ScrollChanged(object sender, ScrollChangedEventArgs e)
- {
- if (++hasUpdateContentOffset >= 2)
- {
- var control = (ScrollViewer)sender;
- double offset = control.VerticalOffset;
- var itemz = muneInfos.Where(s => s.StartPoint <= offset).OrderByDescending(s => s.StartPoint).FirstOrDefault();
- if (itemz != null)
- {
- hasUpdateMune = false;
- itemz.IsSeleted = true;
- hasUpdateMune = true;
- }
- }
- }
- }
- }
3.菜单模型类MuneInfo
- using Prism.Commands;
- using Prism.Mvvm;
- using System;
- using System.Windows.Input;
-
- namespace HT.ControlWPF
- {
- public class MuneInfo : BindableBase
- {
- public event Action<MuneInfo> Checked;
- private bool isSeleted;
- /// <summary>
- /// 是否选中
- /// </summary>
- public bool IsSeleted
- {
- get
- {
- return isSeleted;
- }
- set
- {
- isSeleted = value;
- RaisePropertyChanged(nameof(IsSeleted));
- }
- }
- /// <summary>
- /// 导航地址
- /// </summary>
- public string PageKey { get; set; }
- /// <summary>
- /// 开始位置
- /// </summary>
- public double StartPoint { get; set; }
- /// <summary>
- /// 结束位置
- /// </summary>
- public double EndPoint { get; set; }
- private string title;
- /// <summary>
- /// 标题
- /// </summary>
- public string Title
- {
- get
- {
- return title;
- }
- set
- {
- title = value;
- RaisePropertyChanged(nameof(Title));
- }
- }
- public ICommand CheckedCommand
- {
- get
- {
- return new DelegateCommand(() =>
- {
- Checked?.Invoke(this);
- });
- }
- }
- }
- }
4.使用
- <Window
- x:Class="HT.ControlWPF.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:local="clr-namespace:HT.ControlWPF"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- Title="MainWindow"
- Width="800"
- Height="450"
- FontSize="14"
- mc:Ignorable="d">
- <local:CustomMenuScrollViewer>
- <local:CustomMenuScrollViewer.Content>
- <StackPanel>
- <GroupBox x:Name="groupBox1" Header="功能1标题">
- <Border Height="200" />
- </GroupBox>
- <GroupBox x:Name="groupBox2" Header="功能2标题">
- <Border Height="200" />
- </GroupBox>
- <GroupBox x:Name="groupBox3" Header="功能3标题">
- <Border Height="200" />
- </GroupBox>
- <GroupBox x:Name="groupBox4" Header="功能4标题">
- <Border Height="200" />
- </GroupBox>
- <GroupBox x:Name="groupBox5" Header="功能5标题">
- <Border Height="200" />
- </GroupBox>
- </StackPanel>
- </local:CustomMenuScrollViewer.Content>
- </local:CustomMenuScrollViewer>
- </Window>
注:
1.因为最后那个内容点击第二次才能选中,所以做了一些特殊的处理,还有自己在ItemControl上找不到CheckBox,用了CheckedCommand来触发事件Checked,代码实现有点奇怪,但是功能是已经实现了的,看看那位朋友有好的建议,方便的话和我聊下好的想法。
2.该控件所有代码已经贴出来,可以可以直接使用, 把命名空间改掉就OK。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。