WPF 实现拖曳、扩展面板

2024年2月6日 1331点热度 3人点赞 0条评论
内容纲要

编写界面控件,元素由 Grid 和一些控件组成,其中使用了 Expander 以便可以扩展面板内容。

    <Grid>
        <Grid MinWidth="100" Height="50"
              VerticalAlignment="Top"
              HorizontalAlignment="Left"
              Background="Transparent" Margin="0,0,0,0"
          PreviewMouseDown="Button_MouseDown" 
          PreviewMouseMove="Button_MouseMove" 
          PreviewMouseUp="Button_MouseUp"
              >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50" />
                <ColumnDefinition Width="Auto" />
            </Grid.ColumnDefinitions>
            <Button Content="播放" Grid.Column="0" HorizontalAlignment="Left" />
            <Expander VerticalAlignment="Center" Grid.Column="1" HorizontalAlignment="Left" ExpandDirection="Right" IsExpanded="True">
                <TextBlock TextWrapping="Wrap" FontSize="18">  
                    扩展面板
                </TextBlock>
            </Expander>
        </Grid>
    </Grid>

绑定了以下三个事件,以便可以实现拖曳:

          PreviewMouseDown="Button_MouseDown" 
          PreviewMouseMove="Button_MouseMove" 
          PreviewMouseUp="Button_MouseUp"

必须设置容器的背景颜色,如果不需要颜色,可以设置为透明,但是不能不设置,否则点击容器中的空白位置,事件不会起效。

Background="Transparent" 

另外,容器需要使用指定左上角相对位置,否则不好判断容器是否已在窗口之外:

              VerticalAlignment="Top"
              HorizontalAlignment="Left"

然后使用三个事件实现拖曳功能:

        //鼠标是否按下
        bool _isMouseDown = false;
        //鼠标按下的位置
        Point _mouseDownPosition;
        //鼠标按下控件的Margin
        Thickness _mouseDownMargin;
        //鼠标按下事件
        private void Button_MouseDown(object sender, MouseButtonEventArgs e)
        {
            var c = sender as Panel;

            _mouseDownPosition = e.GetPosition(this);
            var hitTestResult = VisualTreeHelper.HitTest(this, _mouseDownPosition);
            if (hitTestResult != null && hitTestResult.VisualHit != c)
            {
                return;
            }

            _isMouseDown = true;
            _mouseDownMargin = c.Margin;
            c.CaptureMouse();
        }

        private void Button_MouseMove(object sender, MouseEventArgs e)
        {
            var window = this;
            if (_isMouseDown)
            {
                var panel = sender as Panel;
                // 当前鼠标位置
                var pos = e.GetPosition(this);

                // 移动距离
                var dp = pos - _mouseDownPosition;

                double left;
                double top;
                const double right = 0;
                const double bottom = 0;

                // 左边不能小于 0
                left = _mouseDownMargin.Left + dp.X;
                if (left < 0)
                {
                    left = 0;
                }

                var windowWidth = window.ActualWidth - SystemParameters.WindowNonClientFrameThickness.Left - SystemParameters.WindowNonClientFrameThickness.Right;

                if (left + panel.ActualWidth > windowWidth)
                {
                    left = windowWidth - panel.ActualWidth - SystemParameters.WindowNonClientFrameThickness.Left - SystemParameters.WindowNonClientFrameThickness.Right;
                }

                // 顶部不能小于 0
                top = _mouseDownMargin.Top + dp.Y;
                if (top < 0)
                {
                    top = 0;
                }

                // 窗口去除标题栏、底部边框的高度
                var windowHeight = window.ActualHeight - SystemParameters.WindowNonClientFrameThickness.Top - SystemParameters.WindowNonClientFrameThickness.Bottom;
                // 高度还要计算标题栏占用的高度
                if (top + panel.ActualHeight > windowHeight)
                {
                    top = windowHeight - panel.ActualHeight - SystemParameters.WindowNonClientFrameThickness.Bottom * 2;
                }

                panel.Margin = new Thickness(left, top, right, bottom);
            }
        }

        private void Button_MouseUp(object sender, MouseButtonEventArgs e)
        {
            var c = sender as Panel;
            _isMouseDown = false;
            c.ReleaseMouseCapture();
        }

e.GetPosition(this) 表示获取当前用户点击的位置。

e.GetPosition(this)

其中,为了识别容器中的元素,需要判断用户点击的位置是否有其它子元素。
这一步是必须,否则用户点击小面板中的按钮等元素是不会起效,会被面板的拖曳事件处理掉。
这个时候就需要判断,如果点击面板的位置有子元素,则不使用拖曳事件,而是使用子元素的事件。

            var hitTestResult = VisualTreeHelper.HitTest(this, _mouseDownPosition);
            if (hitTestResult != null && hitTestResult.VisualHit != c)
            {
                return;
            }

如果不设置此判断,那么会导致用户点击容器的元素无效,会直接起拖曳效果,而点击子元素的按钮时不会触发子元素的事件。

Button_MouseMove 中代码比较多,里面限制了拖曳的时候元素不能被拉出到窗口外面。

当自定义标题栏,或者使用了第三方 UI 框架时边框大小为 0 时,则需要自行判断是否加上标题栏高度、是否计算边框值。示例如下:

            var window = this;
            if (_isPlayMouseDown)
            {
                var panel = sender as Panel;
                // 当前鼠标位置
                var pos = e.GetPosition(this);

                // 移动距离
                var dp = pos - _playMouseDownPosition;

                double left;
                double top;
                const double right = 0;
                const double bottom = 0;

                // 左边不能小于 0
                left = _playMouseDownMargin.Left + dp.X;
                if (left < 0)
                {
                    left = 0;
                }

                var windowWidth = window.ActualWidth;

                if (left + panel.ActualWidth > windowWidth)
                {
                    left = windowWidth - panel.ActualWidth;
                }

                // 顶部不能小于 0
                top = _playMouseDownMargin.Top + dp.Y;
                if (top < 0)
                {
                    top = 0;
                }

                // 避免遮住标题栏,如果是自定义标题栏,则需要自行使用高度替换 SystemParameters.WindowNonClientFrameThickness.Top
                if (top < SystemParameters.WindowNonClientFrameThickness.Top)
                {
                    top = SystemParameters.WindowNonClientFrameThickness.Top;
                }
                else
                {
                    // 窗口去除标题栏、底部边框的高度
                    var windowHeight = window.ActualHeight;
                    // 高度还要计算标题栏占用的高度
                    if (top + panel.ActualHeight > windowHeight)
                    {
                        top = windowHeight - panel.ActualHeight;
                    }
                }

                panel.Margin = new Thickness(left, top, right, bottom);
            }

痴者工良

高级程序员劝退师

文章评论