admin管理员组

文章数量:1034052

WPF 实现拖曳、扩展面板教程

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

代码语言:javascript代码运行次数:0运行复制
<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>

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

代码语言:javascript代码运行次数:0运行复制
PreviewMouseDown="Button_MouseDown" 
PreviewMouseMove="Button_MouseMove" 
PreviewMouseUp="Button_MouseUp"

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

代码语言:javascript代码运行次数:0运行复制
Background="Transparent" 

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

代码语言:javascript代码运行次数:0运行复制
VerticalAlignment="Top"
HorizontalAlignment="Left"

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

代码语言:javascript代码运行次数:0运行复制
//鼠标是否按下
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) 表示获取当前用户点击的位置。

代码语言:javascript代码运行次数:0运行复制
e.GetPosition(this)

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

代码语言:javascript代码运行次数:0运行复制
var hitTestResult = VisualTreeHelper.HitTest(this, _mouseDownPosition);
if (hitTestResult != null && hitTestResult.VisualHit != c)
{
	return;
}

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

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

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

代码语言:javascript代码运行次数: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);
}

WPF 实现拖曳、扩展面板教程

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

代码语言:javascript代码运行次数:0运行复制
<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>

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

代码语言:javascript代码运行次数:0运行复制
PreviewMouseDown="Button_MouseDown" 
PreviewMouseMove="Button_MouseMove" 
PreviewMouseUp="Button_MouseUp"

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

代码语言:javascript代码运行次数:0运行复制
Background="Transparent" 

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

代码语言:javascript代码运行次数:0运行复制
VerticalAlignment="Top"
HorizontalAlignment="Left"

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

代码语言:javascript代码运行次数:0运行复制
//鼠标是否按下
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) 表示获取当前用户点击的位置。

代码语言:javascript代码运行次数:0运行复制
e.GetPosition(this)

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

代码语言:javascript代码运行次数:0运行复制
var hitTestResult = VisualTreeHelper.HitTest(this, _mouseDownPosition);
if (hitTestResult != null && hitTestResult.VisualHit != c)
{
	return;
}

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

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

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

代码语言:javascript代码运行次数: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);
}

本文标签: WPF 实现拖曳扩展面板教程