Вы не можете отказаться от использования Canvas, но это не значит, что вы должны соблюдать то, как Canvas упорядочивает вещи.
Я создал несколько вариантов поведения, которые могут быть вам полезны.
Первый следующий:
[TypeConstraint(typeof(Canvas))]
public class MouseCursorAction : TargetedTriggerAction<UIElement>
{
protected override void Invoke(object parameter)
{
}
protected override void OnAttached()
{
var canvas = AssociatedObject as Canvas;
if (canvas == null) { return; }
canvas.MouseMove += new MouseEventHandler(canvas_MouseMove);
canvas.MouseEnter += new MouseEventHandler(canvas_MouseEnter);
canvas.MouseLeave += new MouseEventHandler(canvas_MouseLeave);
}
void canvas_MouseLeave(object sender, MouseEventArgs e)
{
Target.Visibility = Visibility.Collapsed;
}
void canvas_MouseEnter(object sender, MouseEventArgs e)
{
Target.Visibility = Visibility.Visible;
}
void canvas_MouseMove(object sender, MouseEventArgs e)
{
var canvas = sender as Canvas;
var positionInCanvas = e.GetPosition(canvas);
Canvas.SetTop(Target, positionInCanvas.Y);
Canvas.SetLeft(Target, positionInCanvas.X);
}
}
С этим поведением вы можете конвертировать любой UIElement в вашем курсоре мыши. Просто установите его на холст и выберите цель, которой вы хотите, чтобы курсор мыши был.
Теперь, чтобы решить ваши проблемы с макетом, я создал другое поведение:
[TypeConstraint(typeof(Canvas))]
public class FillCanvasAction : TargetedTriggerAction<FrameworkElement>
{
protected override void Invoke(object parameter)
{
}
protected override void OnAttached()
{
var canvas = AssociatedObject as Canvas;
if (canvas == null) { return; }
canvas.SizeChanged += new SizeChangedEventHandler(Target_SizeChanged);
}
void Target_SizeChanged(object sender, SizeChangedEventArgs e)
{
var element = Target;
if (element == null) { return; }
var canvas = sender as Canvas;
element.Width = canvas.ActualWidth;
element.Height = canvas.ActualHeight;
Canvas.SetTop(element, 0);
Canvas.SetLeft(element, 0);
}
}
Это поведение при подключении к родительскому холсту изменяет размер целевого FrameworkElement (например, Grid) до размера родительского холста. Это означает, что вы можете создать свой собственный макет внутри этого FrameworkElement, не беспокоясь о том, как Canvas обрабатывает расположение.
Тогда в вашем XAML вы можете сделать что-то вроде этого:
<Grid x:Name="LayoutRoot" Cursor="None">
<Canvas x:Name="canvas" Background="Black">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<local:MouseCursorAction TargetObject="{Binding ElementName=cursorImage}"/>
</i:EventTrigger>
<i:EventTrigger>
<local:FillCanvasAction TargetObject="{Binding ElementName=grid}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid x:Name="grid" Background="White" Width="600" Height="400">
<!-- Create your layout here -->
</Grid>
<Image x:Name="cursorImage" Height="50" Width="50" Source="mouse_cursor.png"/>
</Canvas>
</Grid>
Полный рабочий код можно получить по адресу здесь
Отказ от ответственности: строго говоря, это поведение должно быть реализовано с использованием слабой обработки событий, чтобы избежать утечек памяти; однако эта реализация выходит за рамки этого ответа. Вы можете найти больше информации о слабой обработке событий и о том, понадобится она вам или нет для вашего проекта на этом сайте.