I'm trying to implement "Mega Menu" style menus using WPF. To see examples of mega menus in web design, see here.
So far, I've tried creating a similar interface by using TextBlocks as the highest level of the menu, and then using the mouse hover event to display an additional window that appears positioned below the text block. This is cumbersome and inflexible, future changes would require adding/removing TextBlocks dynamically.
I have considered using the WPF Menu control, because I know the styles can be dramatically modified, but I haven't seen any way to produce multi-column layouts with the hierarchical model that the Menu control uses.
Is there a better way to do this? Am I going to have to stick with custom windows and relative positioning? Can someone point me to an example of this that has already been implemented?
Instead of using custom Windows and positioning, you could use a Popup control. Your can use the StaysOpen=false setting to have it close when the user clicks off-screen.
If you can settle for clicking a menu item instead of hovering, the following custom control will work:
[TemplatePart(Name="PART_HoverArea", Type=typeof(FrameworkElement))]
[TemplatePart(Name="PART_Popup", Type=typeof(Popup))]
public class MegaMenuItem : HeaderedContentControl
{
private FrameworkElement hoverArea;
private Popup popup;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Unhook old template
if (hoverArea != null)
{
hoverArea.PreviewMouseUp -= ShowPopupOnMouseDown;
}
hoverArea = null;
popup = null;
if (Template == null)
return;
// Hook up new template
hoverArea = (FrameworkElement)Template.FindName("PART_HoverArea", this);
popup = (Popup)Template.FindName("PART_Popup", this);
if (hoverArea == null || popup == null)
return;
hoverArea.PreviewMouseUp += ShowPopupOnMouseDown;
}
private void ShowPopupOnMouseDown(object sender, MouseEventArgs e)
{
popup.PlacementTarget = hoverArea;
popup.Placement = PlacementMode.Bottom;
popup.StaysOpen = false;
popup.IsOpen = true;
}
}
You would need a style to display it - something like this. Note the PART_ template part names:
<Style TargetType="WpfApplication14:MegaMenuItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="WpfApplication14:MegaMenuItem">
<Grid>
<Border Name="PART_HoverArea" Background="#fb9c3b" BorderBrush="White" BorderThickness="0,0,1,0">
<ContentPresenter Content="{TemplateBinding Header}" />
</Border>
<Popup
Name="PART_Popup"
PlacementTarget="{Binding ElementName=HoverArea}"
>
<Border MinWidth="100" MaxWidth="400" MinHeight="40" MaxHeight="200" Background="#0d81c3">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The XAML for your menu would then be:
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<WpfApplication14:MegaMenuItem Header="Parent 1">
<WrapPanel Margin="5">
<TextBlock Text="Put any content you want here" Margin="5" />
<TextBlock Text="Put any content you want here" Margin="5" />
<TextBlock Text="Put any content you want here" Margin="5" />
</WrapPanel>
</WpfApplication14:MegaMenuItem>
<WpfApplication14:MegaMenuItem Header="Parent 2">
<WrapPanel Margin="5">
<TextBlock Text="Put any content you want here" Margin="5" />
<TextBlock Text="Put any content you want here" Margin="5" />
<TextBlock Text="Put any content you want here" Margin="5" />
</WrapPanel>
</WpfApplication14:MegaMenuItem>
</StackPanel>
Making the menu appear on hover is much harder, because of the way Popups steal focus (you can show the menu, but you can't easily hide it if they mouse over another menu). For that a custom window might work better.
You could use a HeaderedItemsControl and swap out the Panel to suit your needs; by default it uses a StackPanel however a WrapPanel may suit you better. The pop out and mouse over behavior do not exist by default and would need to be implemented.
A more robust approach would be to leverage a custom Expander; as it provides the pop out behavior you are after and the linked to walkthrough provides the mouse over behavior.
I wonder if the Ribbon control can be retrofitted to do this? It provides tabs, labels, columns and all that.
Please use this UI design sparingly and make sure that it only opens and closes when the user specifically requests such. It's tremendously annoying when a popup mega-menu appears over a website I'm viewing, and I can't get it to close, except for when I want to click on it and it goes away.
Custom windows and relative position are essentially how the WPF Menu/MenuItem control works... but as you've found, it's non-trivial. Best bet would be to retemplate the Menu/MenuItem controls to meet your need.
Related
I have a custom class called FluidPanel that extends Panel and overrides methods MeasureOverride and ArrangeOverride. The goal is to create the Google Keep appearence. Ok, it's working fine. (app link)
But, because I'm extending a basic Panel and using it as the ItemsPanelTemplate, I'm missing 2 things: Reorder and some transitions, that simply doens't work out of the box. See code:
<GridView CanReorderItems="True" CanDrag="True" AllowDrop="True">
<GridView.ItemContainerTransitions>
<TransitionCollection>
<EntranceThemeTransition FromVerticalOffset="200" IsStaggeringEnabled="True"/>
<ReorderThemeTransition/>
</TransitionCollection>
</GridView.ItemContainerTransitions>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<local:FluidPanel/><!--custom panel = reorder doesn't work-->
<!--<StackPanel/>--><!--reorder and animations work normally (reorder to see)-->
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<Grid Width="50" Height="50" Background="Red"/>
<Grid Width="50" Height="50" Background="Green"/>
<Grid Width="50" Height="50" Background="Blue"/>
<Grid Width="50" Height="50" Background="Orange"/>
<Grid Width="50" Height="50" Background="Purple"/>
<Grid Width="50" Height="50" Background="Pink"/>
</GridView>
So, the main question is: How to create a custom Panel with Reorder fully working, including animations? (like that offset to the left/right/...)
A second (less important) question is: And with the EntranceThemeTransition working?
I partially found the solution.
Unfortunately it's not as easy as it should be.
For custom Panels, it seems that we have to implement the Reorder manually, listening to the drag & drop events.
Here is an article about it: Extending GridView with Drag and Drop
and here is a simplified code about it.
The reorder animation is added by manually changing the Visual State:
private void OnDragOver(object sender, DragEventArgs e)
{
var item = (e.OriginalSource as FrameworkElement).DataContext as Note;
if (item == null) return;
e.Data.Properties["targetItem"] = item;
var itemContainer = (sender as ItemsControl).ContainerFromItem(item) as Control;
if (itemContainer == null) return;
VisualStateManager.GoToState(itemContainer, "BottomReorderHint", true);
}
But I still hope there is any easier way to do it, giving the fact that a lot of Panels implement it (StackPanel, ItemsWrapGrid, ...).
Still can't get the EntranceThemeTransition to work on the custom panel.
EDIT: workaround to make EntranceThemeTransition works
I have been reading some tutorials on XAML but it does not help me. I have an empty application window and I need to create 30 TextBoxes in 3 rows.
Being used on the win forms, I thought I would figure it out - well, I did not. I cannot seem to find a way how to create them on certain coordinates.
You first want to place a Canvas control on your screen, then you can populate it with TextBoxes placed at whatever Canvas.Left and Canvas.Top position you want.
That said though, WPF has a much better layout/arrangement system than WinForms, and trying to use it like it's WinForms means you'll miss out on a lot of what makes WPF so great, and you'll be making things a lot harder on yourself.
The WPF way of doing the same thing would be to use an ItemsControl, and a collection of objects that each contain data that the UI needs to to know for display purposes.
First you would create a class to represent each TextBox
public class MyClass
{
public string Text { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
Note: This class should implement INotifyPropertyChanged if you want to change the properties at runtime and have the UI automatically update.
Then make a list of this class, and bind it to an ItemsControl
<ItemsControl ItemsSource="{Binding ListOfMyClass}" />
Then you'd want to overwrite the ItemsPanelTemplate to be a Canvas (the best WPF panel for positioning items according to an X,Y position)
<ItemsControl ItemsSource="{Binding ListOfMyClass}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Next overwrite the ItemTemplate to draw each item using a TextBlock
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
And add an ItemContainerStyle that binds Canvas.Left and Canvas.Top properties to X,Y properties on your object
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
And this will take a List of MyClass objects, and render them to the screen inside a Canvas, with each item positioned at the specified X,Y coordinates.
With all that being said, are you sure this is what you want? WPF has much better layout panels than WinForms, and you don't have to position every element according to an X,Y coordinate if you don't want to.
For a quick visual introduction of WPF's Layouts, I'd recommend this link : WPF Layouts - A Visual Quick Start
Also since it sounds like you're new to WPF and coming from a WinForms background, you may find this answer to a related question useful : Transitioning from Windows Forms to WPF
WPF layout involves choosing a layout container and placing your controls in it. There are several different containers:
The Grid container is a powerful tool for laying out your form in rows and columns. You have complete control over the size of each cell, and you can have rows or columns "span" each other.
The DockPanel container allows you to "dock" controls to the edges of your window or the center. You'd use it to layout a window with smart icon bars, ribbons, status windows, and toolboxes, like Visual Studio itself.
The StackPanel container can be used to stack controls either on top of each other or next to each other
The UniformGrid container is a less powerful version of the container that keeps all cells the same size.
The Canvas container allows you to specify the X & Y coordinates of your controls.
There are one or two others but these are the ones I've used.
The bad thing about laying out a form using X & Y coordinates is that the form does not handle resizing well. This can be exacerbated when you support globalization, as the labels and such for a string may be a lot longer in a foreign language. The best example off the top of my head is Spanish. A lot of English phrases, when translated to Spanish, are a lot longer.
The Grid container gives you the most control over layout. Columns can automatically size themselves to the longest string in the column, while the rest of the columns adjust themselves as necessary, again automatically. You don't have to write one line of code to get that effect; it's all there in the Grid control out of the box.
If you insist on laying out your form the Winforms way, use a Canvas. But you're not going to get the benefit of using the more advanced layout facilities in the other containers, especially the Grid control. I use that almost exclusively in my forms.
EDIT
Using layout controls other than Canvas means that you think about layout differently in WPF than in WinForms. You work at a higher conceptual level and leave the details about figuring out where on the screen a particular control will be displayed to WPF. You also don't have things like the WinForms Anchor property in WPF, which always seemed kind of a hack to me.
The WPF was designed to offer a power and rich framework for designer which make it a different from the classic winforms. You can achieve what want by adding your TextBox control to a canvas and changing the attached property following is a full example illustrating this:
MainWindow
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Canvas Name="mainCanvas" Margin="31,-10,-31,10">
<TextBox Name="myTextBox" Canvas.Left="131" Canvas.Top="109" Height="84" Width="135"></TextBox>
<Button Content="Button" Height="62" Canvas.Left="271" Canvas.Top="69" Width="91" Click="Button_Click"/>
</Canvas>
</Grid>
</Window>
Code Behind
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
myTextBox.SetValue(Canvas.LeftProperty,(double)myTextBox.GetValue(Canvas.LeftProperty)+50.0);
}
}
}
If you want to position the TextBoxes in a grid-way, use Grid:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
...
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" />
<TextBox Grid.Row="0" Grid.Column="1" />
<TextBox Grid.Row="0" Grid.Column="2" />
...
<TextBox Grid.Row="1" Grid.Column="0" />
<TextBox Grid.Row="1" Grid.Column="1" />
<TextBox Grid.Row="1" Grid.Column="2" />
...
</Grid>
I have a user control in Silverlight (Form.xaml) that uses labels to show data. Currently I have the foreground color and visibility of these labels controlled by a template in app.xaml as follows:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
x:Class="TestSilverlight.App"
>
<Application.Resources>
<ControlTemplate x:Key="DataLabel" x:Name="DataLabel" TargetType="sdk:Label">
<sdk:Label Visibility="Visible" Foreground="White" Content="{TemplateBinding Content}"></sdk:Label>
</ControlTemplate>
</Application.Resources>
</Application>
And here is the xaml for the label in Form.xaml:
<sdk:Label Template="{StaticResource DataLabel}" HorizontalAlignment="Left" Margin="140,53,0,0" VerticalAlignment="Top" Content="Ground" FontSize="13.333" Width="138"/>
When I click on the edit button of Form.xaml, I'd like to hide these labels. However, I'm cannot figure out how to change the visibility property in the code behind for this template.
private void EditButton_Click(object sender, RoutedEventArgs e)
{
// Place code to alter template properties here...
}
Any ideas on how to do this? Thank you so much for your help and input.
You could try something like (works using WPF):
<ControlTemplate x:Key="DataLabel" x:Name="DataLabel" TargetType="sdk:Label">
<sdk:Label x:Name="myLabelTemplate" Visibility="Visible" Foreground="White" Content="{TemplateBinding Content}"></sdk:Label>
</ControlTemplate>
(I just gave a name to the label inside the controlTemplate)
<sdk:Label x:Name="myLabel" Template="{StaticResource DataLabel}" HorizontalAlignment="Left" Margin="140,53,0,0" VerticalAlignment="Top" Content="Ground" FontSize="13.333" Width="138"/>
(I just gave a name to the label inside the xaml)
var res = (FindResource("DataLabel") as ControlTemplate).FindName("myLabelTemplate", myLabel);
if (res != null && res is FrameworkElement)
(res as FrameworkElement).Visibility = Visibility.Hidden;
I didn't check to see if FindResource return something not null, and so on (I think you can handle it ;) )
However If I were you I wouldn't use the app resource to put a particular resource of a user control (i'd use the template it in the xaml of the userControl (as an attached resource) instead or even wouldn't use a template at all if you want to modify properties within: it could lead to crash the app because of null pointer exception if not well managed)
I am trying to learn WPF.. Although, Im having trouble with the layouts and which one to choose. I dont want to use canvas because the whole point is the get the hang of WPF..
I have decided to transfer one of my simple programs (in Windows Forms) to WPF..
I have attached the picture of the simple, 1 page form.. Can someone suggest how I could replicate this in WPF?
Form layouts are an interesting predicament. They usually involve a LOT of boilerplate, there's many techniques for removing boilerplate code in form layouts but they're fairly advanced WPF Concepts.
The Simplest Solution for you is going to be a StackPanel for laying out your sections and putting a Grid inside your GroupBox controls.
The Grid can be setup with 4 colunms:
Col 1 Label
Col 1 Body
Col 2 Label
Col 2 Body
With a global style in the resources of your stack panel you can define default visual behaviour so the items dont end up touching:
<Style TargetType="TextBox">
<Setter Property="Margin" "0,0,5,5" />
</Style>
The Above Style will put a 5px margin on the right & bottom of all TextBox controls under it in the visual tree.
This is the absolute simplest (read: straight forward) approach to making this ui in WPF. It is by no means the best, or the most maintainable, but it should be doable in about 10 minutes max.
There are other methods out there for emulating a form layout with WPF like this one or by using other combinations of basic layout components.
For example:
<StackPanel>
<!-- Vertical Stack Panel, Stacks Elements on top of each other -->
<StackPanel Orientation="Horizontal">
<!-- Horizontal Stack Panel, Stacks Elements left to right -->
<Label Width="100">This Label is 100units Wide</Label>
<TextBox />
</StackPanel>
</StackPanel>
Different approaches have different drawbacks, some are flex width, some are not, some play nicely with colunms, some don't. I'd suggest experimenting with the many subclasses of Panel to see what they all do, or you can even roll your own.
Using Grid as container, TextBlock al read-only text, Textbox as editable text and Button.
With these elements and using (for example) XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="MainWindow"
Width="640" Height="480" Background="White">
<Grid>
<TextBlock HorizontalAlignment="Left" Height="20" Margin="34,30,0,0"
TextWrapping="Wrap" Text="Connection String" VerticalAlignment="Top"
Width="107" Foreground="Black"/>
<TextBox HorizontalAlignment="Left" Height="18" Margin="170,32,0,0"
TextWrapping="Wrap" VerticalAlignment="Top" Width="379"/>
<Button Content="Save" HorizontalAlignment="Left" Height="26"
Margin="529,387,0,0" VerticalAlignment="Top" Width="69"/>
</Grid>
you can put all objects in your Window. But if you prefer you can add the elements programmatically. This is the result:
Here an introduction to WPF layout.
Hi at present I am using a grid with Image and two Buttons for showing a custom message box in my WP7 application whose visibility is collapsed at first. All is working fine but I have to disable all the controls behind on the page when its visibility is visible. So its quite a overhead to enable/disable lots of control behind.
Is there a better solution for my requirement which are :(1) To show a message box having image and two button or textbox and (2) It should appear in the middle of page.
Thanks in advance!!
You can use built in Popup control with an attached behaviour written by Kent Boogaart, so it would behave like WPF Popup control with PlacementTarget and Placement:
<Popup b:PopupPlacement.PlacementTarget="{Binding ElementName=someElement}">
<b:Popup.PreferredOrientations>
<b:PopupOrientationCollection>
<b:PopupOrientation Placement="Top" HorizontalAlignment="Center"/>
<b:PopupOrientation Placement="Bottom" HorizontalAlignment="Center"/>
<b:PopupOrientation Placement="Right" VerticalAlignment="Center"/>
<b:PopupOrientation Placement="Right" VerticalAlignment="TopCenter"/>
</b:PopupOrientationCollection>
</b:Popup.PreferredOrientations>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0">My popup's contents</TextBlock>
<Image Grid.Row="1" .... />
</Grid>
</Popup>
See the article Silverlight Popup with Target Placement
Download a project
What I do in this situation is to add a Grid or Border to the page that has a transparent background and IsHitTestVisible = True. You can then add your image etc to the parent control (Grid/Border).
You need to make sure the parent control covers the whole page and then just center the dialog inside this control. When you toggle the visibility of the parent control then the transparent background will overlay the other controls on the page, effectively disabling them.
Here is an example. The uxMessageGrid is the parent control and the Border is the actual dialog. You then just need to make sure this is the last control added to the root element and toggle uxMessageGrid.Visibility in your code.
<Grid x:Name="uxLayoutRoot">
<Other Controls />
<Grid x:Name="uxMessageGrid"
Visibility="Collapsed"
Background="Transparent"
IsHitTestVisible="True">
<Border CornerRadius="0"
BorderThickness="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
BorderBrush="{StaticResource PhoneForegroundBrush}"
Background="{StaticResource PhoneBackgroundBrush}">
<TextBlock Margin="15"
Text="Message..."
TextWrapping="Wrap"/>
</Border>
</Grid>
</Grid>
Use the Custom Dialog box features of the Coding4Fun toolkit
http://coding4fun.codeplex.com/
The toolkit has many controls available beyond the standard Silverlight Toolkit and should more than meet your needs.
Try this one, may be it helps to you
StackPanel st = new StackPanel();
StackPanel st1 = new StackPanel();
Image image = new Image();
image.Height = 300;
image.Width = 300;
image.Source = new BitmapImage(new Uri("/PhoneApp1;component/Koala.jpg", UriKind.Relative));//Build Action=Resource
Button btnok = new Button();
btnok.Content = "Ok";
btnok.Click += new RoutedEventHandler(btnok_Click);
Button btncancel = new Button();
btncancel.Content = "Cancel";
btncancel.Click += new RoutedEventHandler(btncancel_Click);
st1.Orientation = System.Windows.Controls.Orientation.Horizontal;
st1.Children.Add(btnok);
st1.Children.Add(btncancel);
st.Children.Add(image);
st.Children.Add(st1);
ContentPanel.Children.Add(st);