内容目录
编写界面控件,元素由 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);
}
文章评论