WPF学习:Slider — 冒泡显示值

WPF (基础控件9)Slider控件的详细使用教程 WPF(Windows Presentation Foundation)中的Slider控件是一个常用的控件,用于在一个预定的范围内选择一个值。它常用于音量控制、进度条、亮度调节等场景。Slider控件功能强大,支持丰富的属性和事件,可以通过数据绑定、样式定制等方式灵活使用。以下是Slider控件的详细使用教程。 阅读详情

想做一个下图所示的Slider,以冒泡的方式显示其Value值,该怎么做呢?

在这里插入图片描述

功能要求,当鼠标放在滑块上的时候,冒“泡”显示值;当滑块移动的时候,“泡”跟随移动。

看似简单的功能,但要完美实现,确实要花费不少心思的。

方法一:使用Canvas面板。

  1. 改造Slider
    Slider的样式改造,比较简单,重写其模板即可。
  <Slider.Template>
            <ControlTemplate TargetType="Slider">
                <Border Name="border" SnapsToDevicePixels="True"
                        Background="{TemplateBinding Panel.Background}" 
                        BorderBrush="{TemplateBinding Border.BorderBrush}" 
                        BorderThickness="{TemplateBinding Border.BorderThickness}" >
                    <Grid>
                        <Border Background="Gray" Height="4" CornerRadius="2"/>
                        <Track Name="PART_Track" Grid.Row="1">
                            <Track.DecreaseRepeatButton>
                                <RepeatButton>
                                    <RepeatButton.Template>
                                        <ControlTemplate TargetType="RepeatButton">
                                            <Border Height="4" CornerRadius="2" Background="#FF006CBE"/>
                                        </ControlTemplate>
                                    </RepeatButton.Template>
                                </RepeatButton>
                            </Track.DecreaseRepeatButton>

                            <Track.Thumb>
                                <Thumb Name="thumb" VerticalAlignment="Center" Focusable="False">
                                    <Thumb.Template>
                                        <ControlTemplate TargetType="Thumb">
                                            <Grid>
                                                <Ellipse Name="thumbEllipse" Width="10" Height="10" Fill="White" StrokeThickness="1" Stroke="#FF006CBE" />
                                            </Grid>
                                        </ControlTemplate>
                                    </Thumb.Template>
                                </Thumb>
                            </Track.Thumb>
                        </Track>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Slider.Template>
    </Slider>
  1. 添加“泡”
    很明显,这个“泡”的形状、文字都是一个元素,因此需要使用面板来将两个元素组合起来。这里最好使用Canvas,因为它的子元素是绝对定位的,可以通过Canvas.Left、Canvas.Top等属性安排子元素的位置,且Canvas也不会影响父元素的布局。因此将Slider的模板继续改造一下,如下所示:
 <Slider Name="slider" Width="255">
        <Slider.Template>
            <ControlTemplate TargetType="Slider">
                <Border Name="border" SnapsToDevicePixels="True"
                        Background="{TemplateBinding Panel.Background}" 
                        BorderBrush="{TemplateBinding Border.BorderBrush}" 
                        BorderThickness="{TemplateBinding Border.BorderThickness}" >
                    <Grid>
                        <Border Background="Gray" Height="4" CornerRadius="2"/>
                        <Track Name="PART_Track" Grid.Row="1">
                            <Track.DecreaseRepeatButton>
                                <RepeatButton>
                                    <RepeatButton.Template>
                                        <ControlTemplate TargetType="RepeatButton">
                                            <Border Height="4" CornerRadius="2" Background="#FF006CBE"/>
                                        </ControlTemplate>
                                    </RepeatButton.Template>
                                </RepeatButton>
                            </Track.DecreaseRepeatButton>

                            <Track.Thumb>
                                <Thumb Name="thumb" VerticalAlignment="Center" Focusable="False">
                                    <Thumb.Template>
                                        <ControlTemplate TargetType="Thumb">
                                            <Grid>
                                                <Ellipse Name="thumbEllipse" Width="10" Height="10" Fill="White" StrokeThickness="1" Stroke="#FF006CBE" />
                                                <Canvas Name="canvas" Opacity="0">
                                                    <Grid Canvas.Bottom="10" Canvas.Left="-8">
                                                        <Path Width="25" Stretch="Uniform" Fill="White" Stroke="#FF006CBE" StrokeThickness="1" Data="M172,633.14a339.4,339.4,0,0,1-169.9-294C2.13,151.76,154.2-.29,341.57-.29S681,151.76,681,339.11a339.38,339.38,0,0,1-169.9,294h0a183.88,183.88,0,0,0-84.27,106.38h0l-85.26,283.61L256.3,739.52A183.88,183.88,0,0,0,172,633.14Z"/>
                                                        <TextBlock HorizontalAlignment="Center" Margin="0,5,0,0" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Slider}, Path=Value, Converter={StaticResource double2Int}}"/>
                                                    </Grid>
                                                </Canvas>
                                            </Grid>

                                            <ControlTemplate.Triggers>
                                                <Trigger Property="IsMouseOver" Value="True">
                                                    <Setter TargetName="canvas" Property="Opacity" Value="1"/>
                                                </Trigger>
                                                <Trigger Property="IsDragging" Value="True">
                                                    <Setter TargetName="canvas" Property="Opacity" Value="1"/>
                                                </Trigger>
                                            </ControlTemplate.Triggers>
                                        </ControlTemplate>
                                    </Thumb.Template>
                                </Thumb>
                            </Track.Thumb>
                        </Track>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Slider.Template>
    </Slider>

红色部分为添加的代码。有两点需要注意:

  1. TextBlock.Text属性的绑定,使用了RelativeSource模式。

  2. 记得要添加触发器(Trigger),使的Canvas能在指定的状态下显示出来。

以这种方式改的Slider控件,有一个问题:“泡”不能超出窗体显示。也就是说当滑块处于屏幕的边缘时,这个“泡”是显示不出来的。

方法二:使用Window
WPF框架中有Popup控件和Tooltip控件。通过微软的源代码可知,他们的实现都是使用Window控件。即在一定的条件下打开一个Windows,并显示内容,在另一个条件下,则关闭这个Window。

根据这个思路,就可以自定义一个控件,并实现更多的功能

  1. 自定义控件
    控件类:FellowPopup,其他的几个类为辅助类。
/// <summary>
/// 坐标系转换类
/// </summary>
public static class CoordinateHelper
{
    /// <summary>
    /// 将用户空间坐标系的点,转换为屏幕坐标系的点。
    /// 已考虑系统Dpi问题
    /// </summary>
    /// <param name="relativeTo">可视化对象。它的坐标系原点在左上角</param>
    /// <param name="point">在可视化对象坐标系中的点</param>
    /// <returns></returns>
    public static Point ClientToScreen(Visual relativeTo, Point point)
    {
        if (relativeTo == null) return point;
        Point targetToScreen = relativeTo.PointToScreen(point);            // 将point的点转换为屏幕坐标系的点
        var source = PresentationSource.FromVisual(relativeTo);
        double dpiX = 96.0, dpiY = 96.0;
        if (source?.CompositionTarget != null)
        {
            dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
            dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;
        }
        return new Point(targetToScreen.X * 96.0 / dpiX, targetToScreen.Y * 96 / dpiY);
    }
}

public static class DependencyPropertyHelper
{
    /// <summary>
    /// 为依赖属性注册值发生改变时的回调方法
    /// </summary>
    /// <param name="Register">含有依赖属性的对象</param>
    /// <param name="dependencyProperty">依赖属性</param>
    /// <param name="action">要注册的值发生改变时的回调方法</param>
    /// <param name="runOnceImmediately">是否立即执行一次</param>
    public static void RegisterDependencyPropertyChanged(this DependencyObject Register, DependencyProperty dependencyProperty, EventHandler action, bool runOnceImmediately = false)
    {
        DependencyPropertyDescriptor obj = DependencyPropertyDescriptor.FromProperty(dependencyProperty, Register.GetType());
        if(obj != null) obj.AddValueChanged(Register, action);
        if(runOnceImmediately) action?.Invoke(Register,new EventArgs());
    }
}

/// <summary>
/// 放置模式
/// </summary>
public enum PlacementMode
{
    /// <summary>
    /// 顶部靠左
    /// </summary>
    TopLeft,

    /// <summary>
    /// 顶部居中
    /// </summary>
    TopMiddle,

    /// <summary>
    /// 顶部靠右
    /// </summary>
    TopRight,

    /// <summary>
    /// 左侧居中
    /// </summary>
    LeftMiddle,

    /// <summary>
    /// 右侧居中
    /// </summary>
    RightMiddle,

    /// <summary>
    /// 底部靠左
    /// </summary>
    BottomLeft,

    /// <summary>
    /// 底部居中
    /// </summary>
    BottomMiddle,

    /// <summary>
    /// 底部靠右
    /// </summary>
    BottomRight
}

/// <summary>
/// 可跟随控件移动的弹出提示框。
/// </summary>
public class FellowPopup : ContentControl
{
    private Window _popupWnd;

    /// <summary>
    /// 水平位置偏移量
    /// </summary>
    public double HorizontalOffset
    {
        get => (double)GetValue(HorizontalOffsetProperty);
        set => SetValue(HorizontalOffsetProperty, value);
    }

    internal static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register(
        "HorizontalOffset", typeof(double), typeof(FellowPopup), new PropertyMetadata(0.0d, OnValueChanged));

    /// <summary>
    /// 垂直偏移量
    /// </summary>
    public double VerticalOffset
    {
        get => (double)GetValue(VerticalOffsetProperty);
        set => SetValue(VerticalOffsetProperty, value);
    }

    internal static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register(
        "VerticalOffset", typeof(double), typeof(FellowPopup), new PropertyMetadata(0.0d, OnValueChanged));

    /// <summary>
    /// 放置位置
    /// </summary>
    public PlacementMode Placement
    {
        get => (PlacementMode)GetValue(PlacementProperty);
        set => SetValue(PlacementProperty, value);
    }

    internal static readonly DependencyProperty PlacementProperty = DependencyProperty.Register(
        "Placement", typeof(PlacementMode), typeof(FellowPopup), new PropertyMetadata(PlacementMode.BottomRight,OnValueChanged));

    /// <summary>
    /// 放置的目标元素
    /// </summary>
    public FrameworkElement PlacementTarget
    {
        get => (FrameworkElement)GetValue(PlacementTargetProperty);
        set => SetValue(PlacementTargetProperty, value);
    }

    internal static readonly DependencyProperty PlacementTargetProperty = DependencyProperty.Register(
        "PlacementTarget", typeof(FrameworkElement), typeof(FellowPopup), new PropertyMetadata(null,OnValueChanged));

    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FellowPopup popup = (FellowPopup)d;
        popup.UpdateLocation();
    }

    /// <summary>
    /// 是否打开。当其为true时,则显示提示框,false则不显示提示框
    /// </summary>
    public bool IsOpen
    {
        get => (bool)GetValue(IsOpenProperty);
        set => SetValue(IsOpenProperty, value);
    }


    internal static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
        "IsOpen", typeof(bool), typeof(FellowPopup), new PropertyMetadata(false,OnIsOpenChanged));


    private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FellowPopup fp = d as FellowPopup;
        if((bool)e.NewValue ) fp.Open();
        else  fp.Close();
    }
    #region 触发位置更新

    /// <summary>
    /// 触发位置更新的控件
    /// </summary>
    public FrameworkElement TriggerElement
    {
        get => (FrameworkElement)GetValue(TriggerElementProperty);
        set => SetValue(TriggerElementProperty, value);
    }

    internal static readonly DependencyProperty TriggerElementProperty = DependencyProperty.Register(
        "TriggerElement", typeof(UIElement), typeof(FellowPopup), new PropertyMetadata(null, OnTriggerChanged));

    /// <summary>
    /// 触发位置更新的控件的属性
    /// 此属性应该是TriggerElement的属性。
    /// 当此属性的值发生变化时,会触发FellowPopup的UpdateLocation方法。
    /// </summary>
    public DependencyProperty TriggerProperty
    {
        get => (DependencyProperty)GetValue(TriggerPropertyProperty);
        set => SetValue(TriggerPropertyProperty, value);
    }

    internal static readonly DependencyProperty TriggerPropertyProperty = DependencyProperty.Register(
        "TriggerProperty", typeof(DependencyProperty), typeof(FellowPopup), new PropertyMetadata(null, OnTriggerChanged));

    private static void OnTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FellowPopup fp = (FellowPopup)d;
        if (fp.TriggerElement == null || fp.TriggerProperty == null) return;

        // 如果定义属性的类,与TriggerElement类相同,或者TriggerElement类是它的子类
        Type propertyType = fp.TriggerProperty.OwnerType;
        Type elementType = fp.TriggerElement.GetType();

        if ((elementType != propertyType) && (!elementType.IsSubclassOf(propertyType))) throw new ArgumentException("TriggerElement不包含TriggerProperty属性");
        DependencyPropertyHelper.RegisterDependencyPropertyChanged(fp.TriggerElement, fp.TriggerProperty, delegate (object sender, EventArgs args)
        {
            fp.UpdateLocation();
        }, false);
    }
    #endregion

    static FellowPopup()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(FellowPopup), new FrameworkPropertyMetadata(typeof(FellowPopup)));
    }

    /// <summary>
    /// 打开窗体
    /// </summary>
    private void Open()
    {
        _popupWnd = new Window()
        {
            Topmost = true,
            ShowActivated = false,
            AllowsTransparency = true,
            WindowStyle = WindowStyle.None,
            BorderThickness = new Thickness(0),
            SizeToContent = SizeToContent.WidthAndHeight,
            WindowStartupLocation = WindowStartupLocation.Manual,

            Background = this.Background,
            Content = this.Content,
            ContentStringFormat = this.ContentStringFormat,
            ContentTemplate = this.ContentTemplate,
            ContentTemplateSelector = this.ContentTemplateSelector,
        };
        UpdateLocation();
        _popupWnd.Show();
    }

    // 关闭_popup窗体,并回收
    private void Close()
    {
        if (_popupWnd != null)
        {
            _popupWnd.Close();
            GC.SuppressFinalize(_popupWnd);
        }
    }

    /// <summary>
    /// 更新弹出窗体的位置。
    /// </summary>
    private void UpdateLocation()
    {
        if (_popupWnd == null) return;
        if(PlacementTarget == null)
        {
            _popupWnd.Left = 0;
            _popupWnd.Top = 0;
            return;
        }

        double contentWidth = _popupWnd.Content == null ? 0 : ((FrameworkElement)_popupWnd.Content).ActualWidth;
        double contentHeght = _popupWnd.Content == null ? 0 : ((FrameworkElement)_popupWnd.Content).ActualHeight;

        Point targetToScreen;
        switch (Placement)
        {
            case PlacementMode.TopLeft:
                targetToScreen = CoordinateHelper.ClientToScreen(PlacementTarget,new Point(0, 0));
                _popupWnd.Left = targetToScreen.X - contentWidth - HorizontalOffset;
                _popupWnd.Top = targetToScreen.Y - contentHeght - VerticalOffset;
                break;

            case PlacementMode.TopMiddle:
                targetToScreen = CoordinateHelper.ClientToScreen(PlacementTarget, new Point(PlacementTarget.ActualWidth/2, 0));
                _popupWnd.Left = targetToScreen.X - contentWidth / 2 + HorizontalOffset;
                _popupWnd.Top = targetToScreen.Y - contentHeght - VerticalOffset;
                break;

            case PlacementMode.TopRight:
                targetToScreen = CoordinateHelper.ClientToScreen(PlacementTarget, new Point(PlacementTarget.ActualWidth, 0));
                _popupWnd.Left = targetToScreen.X + HorizontalOffset;
                _popupWnd.Top = targetToScreen.Y - contentHeght - VerticalOffset;
                break;

            case PlacementMode.LeftMiddle:
                targetToScreen = CoordinateHelper.ClientToScreen(PlacementTarget, new Point(0, PlacementTarget.ActualHeight / 2));
                _popupWnd.Left = targetToScreen.X - contentWidth - HorizontalOffset;
                _popupWnd.Top = targetToScreen.Y - contentHeght / 2;
                break;

            case PlacementMode.RightMiddle:
                targetToScreen = CoordinateHelper.ClientToScreen(PlacementTarget, new Point(PlacementTarget.ActualWidth, PlacementTarget.ActualHeight / 2));
                _popupWnd.Left = targetToScreen.X + HorizontalOffset;
                _popupWnd.Top = targetToScreen.Y - contentHeght / 2;
                break;

            case PlacementMode.BottomLeft:
                targetToScreen = CoordinateHelper.ClientToScreen(PlacementTarget, new Point(0, PlacementTarget.ActualHeight));
                _popupWnd.Left = targetToScreen.X - contentWidth - HorizontalOffset;
                _popupWnd.Top = targetToScreen.Y + VerticalOffset;
                break;

            case PlacementMode.BottomMiddle:
                targetToScreen = CoordinateHelper.ClientToScreen(PlacementTarget, new Point(PlacementTarget.ActualWidth / 2, PlacementTarget.ActualHeight));
                _popupWnd.Left = targetToScreen.X - contentWidth / 2 - HorizontalOffset;
                _popupWnd.Top = targetToScreen.Y + VerticalOffset;
                break;

            case PlacementMode.BottomRight:
                targetToScreen = CoordinateHelper.ClientToScreen(PlacementTarget, new Point(PlacementTarget.ActualWidth, PlacementTarget.ActualHeight));
                _popupWnd.Left = targetToScreen.X + HorizontalOffset;
                _popupWnd.Top = targetToScreen.Y + VerticalOffset;
                break;
        }
    }
}

(1)在FellowPopup类中,定义了一个Window类型的变量_popup,用于展示“泡”的内容。PlacementTarget、Placement、HorizontalOffset、VerticalOffset四个属性,用于设置_popup的Left和Top属性,以确定_popup在屏幕中的位置。

这里需要注意,Visual.PointToScreen()方法,参数point是在Visual坐标空间的点,其返回值为将这个点,转换为屏幕坐标系的点。但转换后的点,未考虑到系统DPI及屏幕缩放的情况。因此,需要将这个点再运算,使之成为当前屏幕DPI及缩放模式下的点坐标。

(2)TriggerElement、TriggerProperty属性,用于指示,当哪个元素的哪个属性值发生变化时,更新位置。

要想让一个窗体跟着控件移动,就必须告诉窗体,控件当前的位置在哪儿,然后去计算窗体应该在哪儿。而在WPF中,几乎没有控件会实时报告自己的位置。因此,必须找到元素的一个属性,当元素的“位置”发生改变的时候,这个属性值会发生变化。

除了找到这样的一个属性外,还需要注册一个属性值发生改变时的回调方法。在本例中,滑块在滑动的过程中,本身没有属性(公开的)值发生改变,但是Slider的Value属性会发生改变。因此可以选择它,来触发FellowPopup的位置改变。

由此可见,PlacementTarge系列与TriggerElement可以是不同的。一个用于“布置”窗体,一个用于“触发”窗体。

  1. 使用自定义控件
    定义好了控件后,就可以在Xaml中使用它了。继续改造Slider的模板:

     <!-- 别忘了转换器 -->
     <local:Double2IntConverter  x:Key="double2Int" /> 
    
     <Slider Name="slider" Width="255">
         <Slider.Template>
             <ControlTemplate TargetType="Slider">
                 <Border Name="border" SnapsToDevicePixels="True"
                         Background="{TemplateBinding Panel.Background}" 
                         BorderBrush="{TemplateBinding Border.BorderBrush}" 
                         BorderThickness="{TemplateBinding Border.BorderThickness}" >
                     <Grid>
                         <Border Background="Gray" Height="4" CornerRadius="2"/>
                         <Track Name="PART_Track" Grid.Row="1">
                             <Track.DecreaseRepeatButton>
                                 <RepeatButton>
                                     <RepeatButton.Template>
                                         <ControlTemplate TargetType="RepeatButton">
                                             <Border Height="4" CornerRadius="2" Background="#FF006CBE"/>
                                         </ControlTemplate>
                                     </RepeatButton.Template>
                                 </RepeatButton>
                             </Track.DecreaseRepeatButton>
                             <Track.Thumb>
                                 <Thumb Name="thumb" VerticalAlignment="Center" Focusable="False">
                                     <Thumb.Template>
                                         <ControlTemplate TargetType="Thumb">
                                             <Grid>
                                                 <Ellipse Name="thumbEllipse" Width="10" Height="10" Fill="White" StrokeThickness="1" Stroke="#FF006CBE" />
                                                 <local:FellowPopup x:Name="popup" Background="Transparent"
                                                                    Placement="TopMiddle" PlacementTarget="{Binding ElementName=thumbEllipse}"
                                                                    TriggerElement="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Slider}}"
                                                                    TriggerProperty="Slider.Value">
                                                     <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                                                         <Path Width="25" Stretch="Uniform" Fill="White" Stroke="#FF006CBE" StrokeThickness="1" Data="M172,633.14a339.4,339.4,0,0,1-169.9-294C2.13,151.76,154.2-.29,341.57-.29S681,151.76,681,339.11a339.38,339.38,0,0,1-169.9,294h0a183.88,183.88,0,0,0-84.27,106.38h0l-85.26,283.61L256.3,739.52A183.88,183.88,0,0,0,172,633.14Z"/>
                                                         <TextBlock HorizontalAlignment="Center" Margin="0,5,0,0" 
                                                                    Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Slider}, Path=Value, Converter={StaticResource double2Int}}"/>
                                                     </Grid>
                                                 </local:FellowPopup>
                                             </Grid>
                                             <ControlTemplate.Triggers>
                                                 <Trigger Property="IsMouseOver" Value="True">
                                                     <Setter TargetName="popup" Property="IsOpen" Value="True"/>
                                                 </Trigger>
                                                 <Trigger Property="IsDragging" Value="True">
                                                     <Setter TargetName="popup" Property="IsOpen" Value="True"/>
                                                 </Trigger>
                                             </ControlTemplate.Triggers>
                                         </ControlTemplate>
                                     </Thumb.Template>
                                 </Thumb>
                             </Track.Thumb>
                         </Track>
                     </Grid>
                 </Border>
    
             </ControlTemplate>
         </Slider.Template>
     </Slider>
    

复制代码
注意Placement、TriggerElement、TriggerProperty、Text等属性的绑定方式。

至此,改造搞定。以第二种方式改造的Slider,“泡”可以显示在主窗体之上。没有任何东西会挡住他,以为它是TopMost的。另外也可以再添加自己的逻辑去设置“泡”的位置。

以上为自己学习总结,如有错误,请指正。

WPF实现Slider随Thumb位置实时显示Value值的样式 最近实现了一个关于Slider的简单样式,可随Thumb位置实时显示Value值,效果如下面所示: 窗体xaml源码: <Window x:Class="SliderDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="h... 阅读详情

相关推荐

Silverlight UI原理:现代前端框架的底层设计源头

UI框架的核心在于响应式数据流与状态驱动视图的协同机制。从WPF/Silverlight时代沉淀下来的DependencyProperty、INotifyPropertyChanged、RoutedEvent和VisualState等设计,构成了React/Vue/Svelte中受控组件、事件委托、状态管理与声明式动画的底层范式。这些技术并非过时遗产,而是经过工业级验证的UI架构元模型——它解释了为何双向绑定需依赖属性变更通知、为何事件要分隧道/冒泡阶段、为何布局系统必须分离坐标计算与渲染逻辑。理解这些原理

diedangxiang4092的博客 411

WPF Slider 鼠标拖拽处理(拖拽开始和结束事件)

Slider 控件虽然提供了N多鼠标事件,但是却大多数不会触发(MouseDown、MouseUp 等等),不相信的朋友可以试一试。解决方法:Thumb.DragStarted 事件和 Thumb.DragCompleted 事件:请看如下代码

BeanGo的博客 2085

【愚公系列】2023年10月 WPF控件专题 Slider控件详解

WPF控件是Windows Presentation Foundation(WPF)中的基本用户界面元素。它们是可视化对象,可以用来创建各种用户界面。WPF控件可以分为两类:原生控件和自定义控件。原生控件是由Microsoft提供的内置控件,如Button、TextBox、Label、ComboBox等。这些控件都是WPF中常见的标准用户界面元素。自定义控件则允许开发人员使用XAML和C#等编程语言来创建个性化的用户界面元素。自定义控件可以根据需求提供更多的功能和自定义化选项,以及更好的用户体验。

愚公智库 5万+

WPF 控件专题 Slider控件详解

WPF 控件专题 Slider控件详解

^@^lemon tea^@^ 5007

WPF 快速了解并实现美观的Slider自定义控件|Slider详解|示例|效果

本文深入探讨了WPF中Slider控件的自定义技巧,从基本构造到高级视觉效果的实现。首先,展示了进度条的动态效果,随后详细描述了如何设置Slider的Thumb样式和刻度展示。介绍了Slider的核心属性如Value、Minimum、Maximum,并提供了XML示例代码。文章还解释了如何自定义Track和Thumb,并通过代码示例展示了Thumb在不同状态下的视觉效果。此外,探讨了TickBar的自定义方法和两种自定义刻度显示效果的技术。

qq_42399932的博客 3577

WPF滑块控件全解析

Slider(滑块)控件是WPF中常用的一种范围控件,继承自RangeBase抽象类。它允许用户通过拖动滑块在一个指定的数值范围内选择一个值。Slider控件广泛应用于音量调节、亮度控制、进度显示等场景,为用户提供了直观的交互方式。可以通过设置Style来自定义Slider的外观:"Blue""Gray""1"</</</要完全控制Slider的外观,可以通过重写ControlTemplate来实现:</</</</</</</</</</</</注意:自定义模板时,必须保留名为PART_Track和。

冰茶的博客 2235

WPF 控件 (六、Slider)

ScrollBar与Slider 的样式写法一样 只需要修改TargetType 即可。

LyRics1996的博客 1327

SliderWithTickLabels:WPF滑块扩展,带有刻度标记

SliderWithTickLabels WPF控件 这是对Slider 的扩展。 我创建此扩展的目的是允许滑块在刻度线上带有标签。 我对原始Slider所做的最明显的添加是两个属性, TickLabelTemplate ,您可以在其中选择创建自己的标签模板(您甚至可以创建复杂的对象,例如其中包含多个子元素的面板)和GeneratedTicks ,保留刻度值(在不事先知道刻度的情况下,很难生成标签)。 其他属性和控件的其余部分与原始控件相同。 最后但并非最不重要的一点是:标签遵循TickPlacement属性,就像刻度线本身一样。 用法 只需引用文件,它将立即提供给您的XAML编辑器。 例子 下面是有关如何使用SliderWithTickLabels控件的简单示例。 我试图使它们尽可能简单。 当然,您可以进行更精细的构造以满足您的需求。 示例中的custom名称空间定义为xmlns:

【WPF应用27】C#中的Slider控件详解与应用示例

C#中的Slider控件详解与应用示例

qq_35320456的博客 4759

QGIS三维地形可视化实战:从DEM数据到炫酷3D场景(附Qgis2threejs插件配置)

本文详细介绍了如何利用QGIS和Qgis2threejs插件,将DEM数据转化为交互式三维地形场景的完整工作流。核心内容包括DEM数据准备、坐标系选择(重点对比WGS 84与UTM的差异及影响)、插件核心参数配置、卫星影像与等高线叠加等进阶技巧,并提供了成果导出与常见问题解决方案,帮助用户高效创建可用于网页展示的炫酷3D可视化成果。

mqtt6iot的博客 875

【β-Boltzmann探索策略】

𝜀是一个介于0和1之间的参数,用于控制探索和利用的比例。当𝜀较大时,智能体更倾向于探索;当𝜀较小时,智能体更倾向于利用已知信息。

m0_59385870的博客 1085

C# wpf slider实现显示进度、拖动定位、点击定位功能

实现一个播放器,必然需要一个进度条,一个基本的进度条通常显示进度、拖动定位,也可以点击定位,在wpf通常使用slider实现,改变其Value可以显示进度,但是拖动和点击也使用Value或ValueChanged时就会产生冲突,直接死循环了,所以我们需要将这3个功能区分开来,用不同的方式去实现。...

codeofcc的博客 8022

rk3568 rk628桥片 rgb2hdmi

RK628 是瑞芯微全新推出的视频桥接芯片,支持丰富的显示接口,支持高分辨率大屏产品转换需求。基于DRM、FB的显示框架易于使用。 输入信号支持:HDMI、RGB、BT.1120,输出信号支持 HDMI、GVI、Dual LVDS、Dual MIPI、MIPI-CSI、RGB、BT.1120,最高支持4K 60fps 输入/输出转换,并且支持输入到输出 1/4~4倍的缩放。

weixin_35723192的博客 452

WPF中Slider控件的用法与详解

WPF中Slider控件的详细用法,与效果展示!

Wangzhe910113的博客 1965

使用 Simulink 进行电力电子系统的效率优化仿真

电力电子系统(Power Electronics System)是现代电力系统和工业自动化中的关键组成部分,广泛应用于可再生能源发电、电动汽车、不间断电源(UPS)、电机驱动等领域。为了提高系统的整体性能和经济性,必须对电力电子系统的效率进行优化。效率优化的目标是最大化系统的能量转换效率,减少损耗,从而提高系统的可靠性和经济效益。Simulink 提供了一个强大的仿真环境,可以用来设计、分析和验证电力电子系统的效率优化策略。本教程将详细介绍如何使用 Simulink 进行电力电子系统的效率优化仿真。

xiaoheshang_123的博客 355

【AI大模型应用开发】教你用AI轻松将你的知识库变为知识图谱(附完整代码,直接运行)

本文我们来介绍如何使用AI,将自己的知识库自动转换为知识图谱,并进行可视化展示。

同学小张的博客 3927

WPF中Slider控件常用方法

由于Slider是基于RangeBase的,后者又是基于浮点数的,所以滑动值时作出的改变肯定是按浮点数的方式改变的,改变后的结果不一定是整数,为此我们可以吧Slider分成一段一段的,然后每一个段代表一个整数,让Slider仅在这些段中滑动,这个段就是Slider中所谓的”Tick”。最后WPF的Slider还支持把一部分范围的选项高亮化(也就是所谓的SelectionRange:选择范围),没有什么实际意义,仅仅是界面上的高亮,不过还是很有意思的。结果就是无论怎样移动Slider的位置,值都会是整数。

u011328076的博客 791

PLC为西门子的S7-1200,舵机为 DS3218,PLC控制舵机旋转程序实现?

🏆 本文收录于《全栈Bug调优(实战版)》专栏,致力于分享我在项目实战过程中遇到的各类Bug及其原因,并提供切实有效的解决方案。无论你是初学者还是经验丰富的开发者,本文将为你指引出一条更高效的Bug修复之路,助你早日登顶,迈向财富自由的梦想🚀!同时,欢迎大家关注、收藏、订阅本专栏,更多精彩内容正在持续更新中。让我们一起进步,Up!Up!Up!    备注: 部分问题/难题源自互联网,经过精心筛选和整理,结合数位十多年大厂实战经验资深大佬经验总结所得,数条可行方案供所需之人参考。

**My Coding Family** 998

WPF Slider Tickbar 中显示数值

class CustomTickBar : TickBar { protected override void OnRender(System.Windows.Media.DrawingContext dc) { double num = this.Maximum - this.Minimum; double y = this.ReservedSpace * 0.5; ...

weixin_30559481的博客 1280

从0到1打造一台机器人走起来

1)本体机械结构设计,依据人体生物学和生物力学知识,结合机器人运动时各关节扭矩需求,进行合理选用电机,比如胯部需要大扭矩,腿部惯量小等2)硬件拓扑上,分层架构的信号层和功率协同工作,高带宽,低延时,硬实时3)步态规划设计与验证环节实机回溯和部署过程:Real2Sim,Sim2Sim,Sim2Real。

weixin_43163941的博客 243

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

wpf 学习3

wpf学习3

m0_58655601的博客 367

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

WPF Slider控件指定增加固定数值量

例如我在html中定义了一个Slider控件,Value绑定的是double型,在实际拖动中Value的数值会以小数点后N位进行增减,如果想要在拖动中固定增加某个小数,某个整数,则可以使用TickFrequency 刻度属性。 例如我希望滑动条每次增加整数1,则...

misaka_10031的博客 1358

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

继续聊WPF——Slider控件

Slider控件,讲行通俗一点就是我们很常见的滑块,控件的外观上显示一系例刻度值,并存在一个可以被拖动的滑块,用户可以通过拖动滑块来控制控件的值。 如下图所示,是Windows系统的音量调节滑块。 下图所示为QQ音频聊天设置窗口中的音量调节滑块。 这样,我们就以很直观的方式认识了Slider控件。 接下来看看该控件的构成: 然后,查看...

kanwoerzi 981

Slider 控件旋钮上显示数字

Slider 控件旋钮上显示数字

qq_48798329的博客 652

WPF 控件专题 Slider样式模板二

WPF Slider样式模板二

^@^lemon tea^@^ 980
上一篇: WPF Slider 鼠标拖拽处理(拖拽开始和结束事件)
下一篇: Tesseract-OCR命令及WPF简单使用
BeanGo
博客等级 码龄10年 931粉丝 167原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BeanGo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值