C
You don't need to invent a bicycle, a built menu already exists.For example, it is possible to:<Menu VerticalAlignment="Top" HorizontalAlignment="Left"
Background="{x:Static SystemColors.ControlLightBrush}">
<Menu.ItemsPanel>
<!-- вертикальное расположение для меню верхнего уровня -->
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<Menu.ItemContainerStyle>
<!-- центрирование элементов -->
<Style TargetType="MenuItem">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</Menu.ItemContainerStyle>
<MenuItem Header="Шкатулки"/>
<MenuItem Header="Иконы"/>
<MenuItem Header="Панно">
<MenuItem Header="Fürstenzug"/>
<MenuItem Header="Полтавская баталия"/>
</MenuItem>
<MenuItem Header="Распятия"/>
</Menu>
The only thing that works isn't that, I'm going to open down, not right.Unfortunately, there is no direct control of the location. But it's not a big problem.I'll open in a separate room. https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.popup?view=netframework-4.7.1 ♪ Looking in https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.menuitem?view=netframework-4.7.1 We see[TemplatePart(Name = "PART_Popup", Type = typeof(Popup))]
Which means that access to this internal element can be obtained by name "PART_Popup"♪It's the best way to put up the attached property. The idea and realization I pulled out of https://stackoverflow.com/a/1011313/276994 ♪Pick up the attached property:static class MenuExtensions
{
// стандартное attached property
public static PlacementMode GetMenuPlacement(MenuItem menu) =>
(PlacementMode)menu.GetValue(MenuPlacementProperty);
public static void SetMenuPlacement(MenuItem menu, PlacementMode value) =>
menu.SetValue(MenuPlacementProperty, value);
public static readonly DependencyProperty MenuPlacementProperty =
DependencyProperty.RegisterAttached(
"MenuPlacement",
typeof(PlacementMode),
typeof(MenuExtensions),
new FrameworkPropertyMetadata(PlacementMode.Bottom, OnMenuPlacementChanged));
// вызывается при изменении значения
static void OnMenuPlacementChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var menuItem = o as MenuItem;
if (menuItem == null)
return;
if (menuItem.IsLoaded) // загружен => шаблон уже применён
{
UpdatePopupPlacement(menuItem);
}
else // иначе дожидаемся конца загрузки
{
RoutedEventHandler handler = null;
handler = (oo, ee) =>
{
UpdatePopupPlacement(menuItem);
menuItem.Loaded -= handler;
};
menuItem.Loaded += handler;
}
}
static void UpdatePopupPlacement(MenuItem menuItem)
{
if (menuItem.Template.FindName("PART_Popup", menuItem) is Popup popup)
popup.Placement = GetMenuPlacement(menuItem);
}
}
If you're at your disposal, you can put it on. MenuItem:<MenuItem Header="Панно" local:MenuExtensions.MenuPlacement="Right">
(Before and not repeat the code, you can put it in style.)Result: If we need more, for example, to move Popup, we need to complete the code a little. I'm a pro-refactorist. MenuExtensionsIt's what happened.static class MenuExtensions
{
#region attached property PlacementMode MenuPlacement
public static PlacementMode GetMenuPlacement(MenuItem menu) =>
(PlacementMode)menu.GetValue(MenuPlacementProperty);
public static void SetMenuPlacement(MenuItem menu, PlacementMode value) =>
menu.SetValue(MenuPlacementProperty, value);
public static readonly DependencyProperty MenuPlacementProperty =
DependencyProperty.RegisterAttached(
"MenuPlacement",
typeof(PlacementMode),
typeof(MenuExtensions),
new FrameworkPropertyMetadata(
PlacementMode.Bottom,
(o, args) => NowOrOnLoaded(o, UpdatePopupPlacement)));
#endregion
#region attached property Point MenuOffset
public static Point GetMenuOffset(MenuItem menu) =>
(Point)menu.GetValue(MenuOffsetProperty);
public static void SetMenuOffset(MenuItem menu, Point value) =>
menu.SetValue(MenuOffsetProperty, value);
public static readonly DependencyProperty MenuOffsetProperty =
DependencyProperty.RegisterAttached(
"MenuOffset",
typeof(Point),
typeof(MenuExtensions),
new PropertyMetadata(
default(Point),
(o, args) => NowOrOnLoaded(o, UpdatePopupOffset)));
#endregion
static void NowOrOnLoaded(DependencyObject o, Action<MenuItem> a)
{
var menuItem = o as MenuItem;
if (menuItem == null)
return;
if (menuItem.IsLoaded)
{
a(menuItem);
}
else
{
RoutedEventHandler handler = null;
handler = (oo, ee) =>
{
a(menuItem);
menuItem.Loaded -= handler;
};
menuItem.Loaded += handler;
}
}
static void UpdatePopupPlacement(MenuItem menuItem)
{
if (menuItem.Template.FindName("PART_Popup", menuItem) is Popup popup)
popup.Placement = GetMenuPlacement(menuItem);
}
static void UpdatePopupOffset(MenuItem menuItem)
{
if (menuItem.Template.FindName("PART_Popup", menuItem) is Popup popup)
{
var offset = GetMenuOffset(menuItem);
popup.HorizontalOffset = offset.X;
popup.VerticalOffset = offset.Y;
}
}
}
They may be used as follows:<MenuItem Header="Панно"
local:MenuExtensions.MenuPlacement="Right"
local:MenuExtensions.MenuOffset="15,15"> ...
Update: I found a bang in MenuExtensions (never you can trust someone else's code!) and fixed it. But it's a complex patter with a subscription and an opi. Task♪static class MenuExtensions
{
#region attached property PlacementMode MenuPlacement
public static PlacementMode GetMenuPlacement(MenuItem menu) =>
(PlacementMode)menu.GetValue(MenuPlacementProperty);
public static void SetMenuPlacement(MenuItem menu, PlacementMode value) =>
menu.SetValue(MenuPlacementProperty, value);
public static readonly DependencyProperty MenuPlacementProperty =
DependencyProperty.RegisterAttached(
"MenuPlacement",
typeof(PlacementMode),
typeof(MenuExtensions),
new FrameworkPropertyMetadata(
PlacementMode.Bottom,
(o, args) => WhenPopupAvailable(o, UpdatePopupPlacement)));
#endregion
#region attached property Point MenuOffset
public static Point GetMenuOffset(MenuItem menu) =>
(Point)menu.GetValue(MenuOffsetProperty);
public static void SetMenuOffset(MenuItem menu, Point value) =>
menu.SetValue(MenuOffsetProperty, value);
public static readonly DependencyProperty MenuOffsetProperty =
DependencyProperty.RegisterAttached(
"MenuOffset",
typeof(Point),
typeof(MenuExtensions),
new PropertyMetadata(
default(Point),
(o, args) => WhenPopupAvailable(o, UpdatePopupOffset)));
#endregion
static Task TillEvent(
Action<RoutedEventHandler> subscribe, Action<RoutedEventHandler> unsubscribe)
{
var tcs = new TaskCompletionSource<bool>();
RoutedEventHandler handler = null;
handler = (o, e) =>
{
tcs.TrySetResult(true);
unsubscribe(handler);
};
subscribe(handler);
return tcs.Task;
}
static async void WhenPopupAvailable(DependencyObject o, Action<Popup, MenuItem> a)
{
var menuItem = o as MenuItem;
if (menuItem == null)
return;
if (!menuItem.IsLoaded) // не загружен => дожидаемся
await TillEvent(h => menuItem.Loaded += h, h => menuItem.Loaded -= h);
Popup popup = (Popup)menuItem.Template.FindName("PART_Popup", menuItem);
if (popup == null) // нет ещё попапа?
{
var parent = menuItem.Parent as MenuItem;
if (parent == null) // если нет родителя, тогда непонятна причина, выходим
return; // для отладки лучше бросить исключение тут
// если причина в том, что родитель закрыт, подождём пока откроется
if (!parent.IsSubmenuOpen)
await TillEvent(h => parent.SubmenuOpened += h,
h => parent.SubmenuOpened -= h);
}
popup = (Popup)menuItem.Template.FindName("PART_Popup", menuItem);
if (popup != null) // если и тут нету, у нас не вышло его достать
a(popup, menuItem);
}
static void UpdatePopupPlacement(Popup popup, MenuItem menuItem)
{
popup.Placement = GetMenuPlacement(menuItem);
}
static void UpdatePopupOffset(Popup popup, MenuItem menuItem)
{
var offset = GetMenuOffset(menuItem);
popup.HorizontalOffset = offset.X;
popup.VerticalOffset = offset.Y;
}
}
This code of attached property can be placed not only on Panno, but also on the Polawa Batalia.To paint the background, you need a code like this:<Menu VerticalAlignment="Top" HorizontalAlignment="Left" Background="Blue">
<Menu.Resources>
<Style TargetType="MenuItem" x:Key="MenuItemBase">
<Setter Property="Background" Value="Blue"/>
<Setter Property="BorderBrush" Value="Black"/>
</Style>
<Style TargetType="MenuItem" BasedOn="{StaticResource MenuItemBase}"/>
</Menu.Resources>
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource MenuItemBase}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Border>
<TextBlock Text="{Binding}" HorizontalAlignment="Center"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Menu.ItemContainerStyle>
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Menu.ItemsPanel>
and above.