Why is Canvas covering peer controls in Dockpanel? - wpf

Why is the Canvas covering the other Children of the Dock Panel?
I'm setting up a menu bar at the top of the client area and a status bar at the bottom of the client area of the window as per standard convention in xaml as follows:
<Window x:Class="RichCoreW.ScenEditWnd"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ScenEditWnd" Height="490" Width="776" HorizontalAlignment="Right">
<DockPanel Name="mapDockP">
<Menu IsMainMenu="True" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Save" Name="menuISave" Click="menuISave_Click"/>
<MenuItem Header="Make Playable" Click="MakePlayable" />
</MenuItem>
<MenuItem Command="ApplicationCommands.Help" />
</Menu>
<StackPanel DockPanel.Dock="Bottom" Name="stackPanel1" Orientation="Horizontal" background="Yellow">
<Label Content="Playable:" Name="label1" />
<Label Name="labPlayable" />
</StackPanel>
</DockPanel>
</Window>
Then I add a an instance of the MapCanvEdit Class which inherits from Canvas in C# code as follows. As its the last child to be added to the Dockpanel it should take the remaining space in the Dockpanel. But it covers the menu and status bars as well covering the whole of the client area. To be precise it is the children of the Canvas that cover over the other two Stack Panels. Where the Canvas(MapCanvEdit) is empty you can see the Menu and Status bars:
public partial class ScenEditWnd : Window
{
ScenC scenC;
MapCanvEdit mapCanvE;
public ScenEditWnd(ScenC scenCI)
{
InitializeComponent();
scenC = scenCI;
mapCanvE = new MapCanvEdit(scenC);
mapDockP.Children.Add(mapCanvE);
MouseWheel += mapCanvE.Zoom;
mapCanvE.SizeChanged += delegate { mapCanvE.DrawHexs(); };
ContentRendered += delegate { mapCanvE.DrawHexs(); };
labPlayable.Content = scenC.playable.ToString();
}
}
I've left out the other methods for simplicity. Any help appreciated!

It's just the way Canvas works. It can place its children outside its own area. If you want it to restrict children to bounds set ClipToBounds="True" (see ClipToBounds on MSDN) or use another panel.

Related

AvalonDock Not rendering layout when using default layout serialization and deserialization

i am just getting started with AvalonDock and i have been beating my head on the wall for a few days now. I am trying to do the mundane task of loading my layout from the de-serialized dockingmanager. I have attempted what ("AvalonDock DockingManager does not load layout") this post has suggested and am still getting a empty screen.
my wpf application xaml is below:
<Window x:Name="frm_Main" x:Class="DataCAD.Forms.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:aD="http://schemas.xceed.com/wpf/xaml/avalondock"
Title="MainWindow" Height="563.9" Width="832" WindowState="Maximized">
<Grid Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="0*"/>
</Grid.ColumnDefinitions>
<Menu HorizontalAlignment="Left" Height="24" VerticalAlignment="Top" Width="{Binding ElementName=frm_Main,Path=ActualWidth}">
<MenuItem Name="MnuI_File" Height="21" Width="35" HeaderStringFormat="" Header="File" >
<MenuItem Name="mnuFile_Importcui" Padding="15,3,3,3" Header="Import Cuix" Click="MnuFile_Importcui_OnClick">
<MenuItem.Icon>
<Image Width="24" Height="24" Source="/Images/cuiImport.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Name="mnuFile_Exportcui" Padding="15,3,3,3" Header="Export Cuix" Click="MnuFile_Exportcui_OnClick">
<MenuItem.Icon>
<Image Width="24" Height="24" Source="/Images/cuiExport.png"/>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Name="mnuFile_Exit" Padding="15,3,3,3" Header="Exit" Click="MnuFile_Exit_OnClick"/>
</MenuItem>
<MenuItem Name="MnuI_Edit" Height="21" Width="35" HeaderStringFormat="" Header="Edit" ></MenuItem>
<MenuItem Name="MnuI_View" Height="21" Width="42" HeaderStringFormat="" Header="View" >
<MenuItem Name="mnuView_Cuixexplorer" IsChecked="True" IsCheckable="True" Padding="15,3,3,3" Header="Cuix Explorer" Click="MnuView_Cuixexplorer_OnClick"/>
</MenuItem>
</Menu>
<aD:DockingManager x:Name="Dockman" Margin="0,21,1,0">
<aD:DockingManager.Theme>
<aD:AeroTheme/>
</aD:DockingManager.Theme>
<aD:LayoutRoot>
<aD:LayoutPanel Orientation="Vertical">
<aD:LayoutPanel Orientation="Horizontal">
<aD:LayoutAnchorablePaneGroup DockWidth="150" Orientation="Vertical">
<aD:LayoutAnchorablePane x:Name="CuixExplorerAnchor" DockWidth="150">
<aD:LayoutAnchorable x:Name="CuixExplorer" ContentId="CuixExplorer" Title="Cuix Explorer">
<TreeView Name="trv_CuixDisplay" AllowDrop="True" Drop="trv_CuiDisplay_onDrop">
</TreeView>
</aD:LayoutAnchorable>
</aD:LayoutAnchorablePane>
<aD:LayoutAnchorablePane x:Name="Gen_PropertiesAnchor" DockWidth="150">
<aD:LayoutAnchorable x:Name="Gen_Properties" ContentId="Properties" Title="Properties">
<Grid x:Name="grid"></Grid>
</aD:LayoutAnchorable>
</aD:LayoutAnchorablePane>
</aD:LayoutAnchorablePaneGroup>
<aD:LayoutDocumentPaneGroup Orientation="Vertical" DockWidth="*" x:Name="DocPane">
<aD:LayoutDocumentPane x:Name="DockingMainWindow">
<aD:LayoutDocument ContentId="default" Title="default">
<RichTextBox x:Name="DefaultTextBox"></RichTextBox>
</aD:LayoutDocument>
</aD:LayoutDocumentPane>
</aD:LayoutDocumentPaneGroup>
</aD:LayoutPanel>
<aD:LayoutAnchorablePaneGroup Orientation="Vertical" DockHeight="150" >
<aD:LayoutAnchorablePane DockHeight="150" >
<aD:LayoutAnchorable ContentId="output" Title="Output">
<TextBox x:Name="OutpuTextBox"></TextBox>
</aD:LayoutAnchorable>
</aD:LayoutAnchorablePane>
</aD:LayoutAnchorablePaneGroup>
</aD:LayoutPanel>
</aD:LayoutRoot>
</aD:DockingManager>
</Grid>
My setting xml is below:
<?xml version="1.0" encoding="utf-8"?>
<LayoutRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RootPanel Orientation="Vertical">
<LayoutPanel Orientation="Horizontal">
<LayoutAnchorablePaneGroup Orientation="Vertical" DockWidth="150">
<LayoutAnchorablePane DockWidth="150">
<LayoutAnchorable AutoHideMinWidth="100" AutoHideMinHeight="100" Title="Cuix Explorer" IsSelected="True" ContentId="CuixExplorer" />
</LayoutAnchorablePane>
<LayoutAnchorablePane DockWidth="150">
<LayoutAnchorable AutoHideMinWidth="100" AutoHideMinHeight="100" Title="Properties" IsSelected="True" ContentId="Properties" />
</LayoutAnchorablePane>
</LayoutAnchorablePaneGroup>
<LayoutDocumentPaneGroup Orientation="Vertical">
<LayoutDocumentPane>
<LayoutDocument Title="default" IsSelected="True" IsLastFocusedDocument="True" ContentId="default" LastActivationTimeStamp="10/05/2013 00:30:01" />
</LayoutDocumentPane>
</LayoutDocumentPaneGroup>
</LayoutPanel>
<LayoutAnchorablePaneGroup Orientation="Vertical" DockHeight="150">
<LayoutAnchorablePane DockHeight="150">
<LayoutAnchorable AutoHideMinWidth="100" AutoHideMinHeight="100" Title="Output" IsSelected="True" ContentId="output" />
</LayoutAnchorablePane>
</LayoutAnchorablePaneGroup>
</RootPanel>
<TopSide />
<RightSide />
<LeftSide />
<BottomSide />
<FloatingWindows />
<Hidden />
</LayoutRoot>
my code is below:
public MainWindow()
{
InitializeComponent();
LoadLayout();
Left = Settings.Default.MAINWINDOW_LEFT;
Top = Settings.Default.MAINWINDOW_TOP;
Width = Settings.Default.MAINWINDOW_WIDTH;
Height = Settings.Default.MAINWINDOW_HEIGHT;
WindowState = (Settings.Default.MAINWINDOW_ISMAXIMIZED) ? WindowState.Maximized : WindowState.Normal;
}
private void MainWindow_OnClosing(object sender , CancelEventArgs cancelEventArgs)
{
Settings.Default.MAINWINDOW_LEFT = Left;
Settings.Default.MAINWINDOW_TOP = Top;
Settings.Default.MAINWINDOW_WIDTH = Width;
Settings.Default.MAINWINDOW_HEIGHT = Height;
Settings.Default.MAINWINDOW_ISMAXIMIZED = (WindowState == WindowState.Maximized);
Settings.Default.CUIXEXPLORER_ISVISIBLE = CuixExplorer.IsVisible;
Settings.Default.CUIXEXPLORER_DOCKLOCATION = CuixExplorer.PreviousContainerIndex;
Settings.Default.CUIXEXPLORER_WIDTH = CuixExplorer.FloatingWidth;
Settings.Default.CUIXEXPLORER_HEIGHT = CuixExplorer.FloatingHeight;
Settings.Default.Save();
SaveLayout();
}
private void SaveLayout()
{
var serializer = new XmlLayoutSerializer(Dockman);
using (var stream = new StreamWriter(_settingsFile))
{
serializer.Serialize(stream);
}
}
private void LoadLayout()
{
var serializer = new XmlLayoutSerializer(Dockman);
using (var stream = new StreamReader(_settingsFile))
{
serializer.Deserialize(stream);
}
}
any help is greatly appreciated. Thanks in advance.
The reason i was not able to Deserialize my layout was because i was missing the Serializer.LayoutSerializationCallback event in my LoadLayout method.
new LoadLayout method:
public void LoadLayout()
{
var serializer = new XmlLayoutSerializer(DockingManagerMain);
// Imparitive for Deserialization
serializer.LayoutSerializationCallback += (s, args) =>
{
args.Content = args.Content;
};
serializer.Deserialize(DockingLayoutConfig);
}
I have come across this issue and the problem is not the callback. It is unnecessary. The method to Deserialize will NOT work before Window_Loaded event happen. If you call it before like in this case in the window constructor the object is not fully loaded therefore cannot load properly the xml. Doing so will cause the white screen issue.
I came upon this error while switching from Syncfusion Docking Manager to Exceed Avalon Dock. I was changing the code for the loading and saving only and not where they are called from. Syncfusion dock manager loading DOES work when called f the constructor and this one obviously is not working there. No big deal, Window_Loaded do the job.
Solution from Franck works with the UserControl_Loaded event in codebehind.
a nicer thing is inherit your viewmodel from Screen and override the OnViewLoaded(object view) method. in your xaml from your view give your dockmanager a x:Name so that you can use it in the OnViewLoaded method.
Something like this (combination of Franck with Trae Moore's code):
protected override void OnViewLoaded(object view)
{
base.OnViewLoaded(view);
MyView v = view as MyView;
if (v?.myDockManager != null) {
var ser = new XmlLayoutSerializer(v.myDockManager);
// ...
}
}

WPF Popup MenuItem stays highlighted when using arrow keys

After opening a Popup menu programatically, if the user uses up and down arrow keys to move through the menu, menu items get highlighted and they never get unhighlighted. What can I do so that after the user presses the down arrow, the previously highlighted menuitem becomes unhighlighted?
This happens with a very simple Popup menu:
<Grid>
<Button x:Name="Button1" Content="Open Menu"
Click="OnPopupMenuButton_Click"
Height="23" HorizontalAlignment="Left" Margin="69,12,0,0" VerticalAlignment="Top" Width="75" />
<Popup x:Name="MyPopupMenu" StaysOpen="False" >
<StackPanel Orientation="Vertical" Background="White" Margin="0">
<MenuItem x:Name="xAimee" Header="Aimee" Margin="0,2,0,0" />
<MenuItem x:Name="xBarbara" Header="Barbara" />
<MenuItem x:Name="xCarol" Header="Carol" />
<Separator x:Name="xSeparator1" Margin="0,2,2,2"/>
<MenuItem x:Name="xDana" Header="Dana" />
<MenuItem x:Name="xElizabeth" Header="Elizabeth" />
</StackPanel>
</Popup>
</Grid>
Here is how the Popup gets opened:
private void OnPopupMenuButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
Button button = sender as Button;
MyPopupMenu.PlacementTarget = button;
MyPopupMenu.Placement = PlacementMode.Mouse;
MyPopupMenu.IsOpen = true;
MyPopupMenu.StaysOpen = false;
}
I have been following up on archer's suggestion, but I had a few issues. First, I did not want the menu to open on a right-click, partly because I just didn't want it to open on a right-click and partly because I actually need to use PlacementMode.Top, and the context menu kept opening in the standard context-menu place (to the side and down).
So in the end, I did end up using a Context Menu, but I did a couple of special things. First, in the Window constructor, I set the button's ContextMenu to null, to prevent it from opening when right-clicked. Then when the user left-clicks, I programmatically set the ContextMenu to the one that I created in the xaml file. When the menu closes, I set the button's ContextMenu back to null. I tried manipulating the ContextMenu visibility instead, but that did not seem to work as well as setting it to null and back to an object.
Here is the final xaml, not too different from the question exception that I am handling the Closed event for the ContextMenu.
<Button x:Name="xOpenContextMenuButton" Content = "Open Menu"
Click="OnContextMenuButton_Click"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
Width="80" Margin="0,0,36,8" Height="23">
<Button.ContextMenu>
<ContextMenu x:Name="xContextMenu" Closed="OnContextMenu_Closed">
<MenuItem x:Name="xAimee" Header="Aimee" />
<MenuItem x:Name="xBarbara" Header="Barbara" />
<MenuItem x:Name="xCarol" Header="Carol" />
<Separator x:Name="xSeparator1" Margin="0,2,2,2" />
<MenuItem x:Name="xDana" Header="Dana" />
<MenuItem x:Name="xElizabeth" Header="Elizabeth" />
</ContextMenu>
</Button.ContextMenu>
</Button>
Here is the code-behind, which changed a lot:
public MainWindow()
{
InitializeComponent();
xOpenContextMenuButton.ContextMenu = null;
}
private void OnContextMenuButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
xOpenContextMenuButton.ContextMenu = xContextMenu;
xContextMenu.PlacementTarget = xOpenContextMenuButton;
xContextMenu.Placement = PlacementMode.Top;
xContextMenu.IsOpen = true;
xContextMenu.StaysOpen = false;
}
private void OnContextMenu_Closed(object sender, RoutedEventArgs e)
{
xOpenContextMenuButton.ContextMenu = null;
}
Once again, thanks to archer, because I didn't realize that using Popup was not the normal way to create a popup menu in WPF. I think the root cause of the problem is, a Popup can contain anything -- a label, another button, etc. Popup isn't necessarily expecting embedded MenuItems, so it isn't smart enough to understand that it should switch between my menu items when using the arrow keys. But a ContextMenu expects to have MenuItems in it so it knows how to switch between them.

Change the content of a button at runtime in wpf

I have a button in a wpf (silverlight actually) application.
I want to change the content of this button at runtime in order to add an image to it (for example, if content was "button one", i want the content to become: stackpanel containing image1 + original button text).
Please help.
Check this:
var sp = new StackPanel();
var img = new Image() {Source = ...}
sp.Children.Add(img);
sp.Children.Add("Hello world");
btn.Content = sp; // btn - is the name of your button.
Instead of adding the image, hide and show it using BooleanToVisibilityConverter. ShowImage is a bool property that you set to true/false to show/hide the image.
<Button>
<StackPanel Orientation="Horizontal">
<Image Visibility="{Binding Path=ShowImage, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Margin="5,0,0,0" Text="button one" />
</StackPanel>
</Button>

How can I make the Silverlight ScrollViewer scroll to show a child control with focus?

I have a ScrollViewer which contains a Grid with multiple controls in it. The user can tab through the controls, but eventually they tab to a control that isn't in view - so they have to manully scroll to make the control visible again.
Is there any way to make the ScrollViewer scroll automatically so that the focussed control is always visible. Failing that, is there any way I can make this work, short of listening to a GotFocus event on every control and then scrolling the ScrollViewer to make the control visible?
At present I'm using Silverlight 2.
I tested this using Silverlight 3. I am not sure about SL2.
This is my XAML:
<ScrollViewer Height="200" Width="200" KeyUp="ScrollViewer_KeyUp">
<StackPanel>
<Button Content="1" Height="20" />
<Button Content="2" Height="20" />
<Button Content="3" Height="20" />
<Button Content="4" Height="20" />
<Button Content="5" Height="20" />
<Button Content="6" Height="20" />
<Button Content="7" Height="20" />
<Button Content="8" Height="20" />
<Button Content="9" Height="20" />
<Button Content="10" Height="20" />
<Button Content="11" Height="20" />
<Button Content="12" Height="20" />
<Button Content="13" Height="20" />
<Button Content="14" Height="20" />
<Button Content="15" Height="20" />
<Button Content="16" Height="20" />
<Button Content="17" Height="20" />
<Button Content="18" Height="20" />
<Button Content="19" Height="20" />
<Button Content="20" Height="20" />
</StackPanel>
</ScrollViewer>
And this is the code-behind:
private void ScrollViewer_KeyUp(object sender, KeyEventArgs e)
{
ScrollViewer scrollViewer = sender as ScrollViewer;
FrameworkElement focusedElement = FocusManager.GetFocusedElement() as FrameworkElement;
GeneralTransform focusedVisualTransform = focusedElement.TransformToVisual(scrollViewer);
Rect rectangle = focusedVisualTransform.TransformBounds(new Rect(new Point(focusedElement.Margin.Left, focusedElement.Margin.Top), focusedElement.RenderSize));
double newOffset = scrollViewer.VerticalOffset + (rectangle.Bottom - scrollViewer.ViewportHeight);
scrollViewer.ScrollToVerticalOffset(newOffset);
}
What I did was to click on Button #1 and tab until I get to Button #20. It worked for me. Give it a try and let me know how it works for you.
The silverlight toolkit contains a method "ScrollIntoView".
Add a reference to System.Windows.Controls.Toolkit.dll ans you should be able to use the code below.
scrollViewer.ScrollIntoView(control);
Just a slight enhancement. Still need to do this for Silverlight 4 by the way.
Instead of GotFocus for each control you can handle the GotFocus of the scrollviewer itself and implement it just once.
private void _ScrollViewer_GotFocus(object sender, RoutedEventArgs e)
{
FrameworkElement element = e.OriginalSource as FrameworkElement;
if (element != null)
{
ScrollViewer scrollViewer = sender as ScrollViewer;
scrollViewer.ScrollToVerticalOffset(GetVerticalOffset(element, scrollViewer));
}
}
private double GetVerticalOffset(FrameworkElement child, ScrollViewer scrollViewer)
{
// Ensure the control is scrolled into view in the ScrollViewer.
GeneralTransform focusedVisualTransform = child.TransformToVisual(scrollViewer);
Point topLeft = focusedVisualTransform.Transform(new Point(child.Margin.Left, child.Margin.Top));
Rect rectangle = new Rect(topLeft, child.RenderSize);
double newOffset = scrollViewer.VerticalOffset + (rectangle.Bottom - scrollViewer.ViewportHeight);
return newOffset < 0 ? 0 : newOffset; // no use returning negative offset
}
I got this to work, with help of Kiril's answer above. The general context of this is that I have user-defineable forms in my application, and this code is used for rendering the controls on a form.
My general strategy was to add my controls to a Grid, then find all the children of the ScrollViewer using VisualTreeHelper, and add a GotFocus event handler to each control.
When the control gets focus, again using VisualTreeHelper, I search up the visual tree to find the control whose parent is the Grid that is being scrolled by the ScrollViewer. Then I scroll the ScrollViewer to make the control visible.
Here's the code (gridRender is the Grid that the controls are added to):
private void AfterFormRendered()
{
var controls = VisualTreeHelperUtil.FindChildren<Control>(gridRender);
foreach (var ctrl in controls)
{
ctrl.GotFocus += CtrlGotFocus;
}
}
private void CtrlGotFocus(object sender, RoutedEventArgs e)
{
var ctrl = sender as Control;
var gridChildControl = VisualTreeHelperUtil.FindParentWithParent(ctrl, gridRender) as FrameworkElement;
if (gridChildControl != null)
{
// Ensure the control is scrolled into view in the ScrollViewer.
GeneralTransform focusedVisualTransform = gridChildControl.TransformToVisual(scrollViewer);
Point topLeft = focusedVisualTransform.Transform(new Point(gridChildControl.Margin.Left, gridChildControl.Margin.Top));
Rect rectangle = new Rect(topLeft, gridChildControl.RenderSize);
double newOffset = scrollViewer.VerticalOffset + (rectangle.Bottom - scrollViewer.ViewportHeight);
scrollViewer.ScrollToVerticalOffset(newOffset);
}
}
Note: the VisualTreeHelperUtil class is my own class that adds some useful searching functionality to the VisualTreeHelper class.

constrain StackPanel's width to smallest child element

How can I constrain a vertical WPF StackPanel's width to the most narrow item it contains. The StackPanel's width must not be greater than the width of any other child element.
Unfortunately the IValueConverter approach will not always work; if the children are added to StackPanel statically, for example, the child collection will be empty at the time of binding (so I discovered). The simplest solution is to create a custom panel:
public class ConstrainedStackPanel : StackPanel
{
public ConstrainedStackPanel()
{
}
protected override Size MeasureOverride(Size constraint)
{
foreach (var item in this.Children)
{
// FrameworkElement has the Width property we care about.
FrameworkElement element = item as FrameworkElement;
if (element != null)
constraint.Width = Math.Min(element.Width, constraint.Width);
}
return base.MeasureOverride(constraint);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
foreach (var item in this.Children)
{
// FrameworkElement has the Width property we care about.
FrameworkElement element = item as FrameworkElement;
if (element != null)
arrangeSize.Width = Math.Min(element.Width, arrangeSize.Width);
}
return base.ArrangeOverride(arrangeSize);
}
}
You can use the panel as illustrated by the following XAML:
<StackPanel Margin="5">
<TextBlock Text="StackPanel:" FontWeight="Bold" />
<StackPanel x:Name="panelA">
<Button Width="100" Content="100" />
<Button Width="200" Content="200" />
<Button Width="300" Content="300" />
<Button Width="400" Content="400" />
</StackPanel>
<TextBlock Text="ConstrainedStackPanel:" FontWeight="Bold" Margin="0,10,0,0" />
<l:ConstrainedStackPanel x:Name="panelB">
<Button Width="100" Content="100" />
<Button Width="200" Content="200" />
<Button Width="300" Content="300" />
<Button Width="400" Content="400" />
</l:ConstrainedStackPanel>
</StackPanel>
Which will render something like the following:
I hope this helps.
i have tried binding to the ActualWidth property, even creating a converter to offset the value, and this works great with one exception: as you expand the size of the container, the width is updated properly, but when to make the container smaller, the width of the actual content gets smaller, but the "page width" of any scrollviewers will not. i'm sure there's a way to work around this, but i haven't found it.
You can't. A vertically oriented StackPanel will always allocate as much width as its children request.
You'd be best off writing a custom panel to achieve your desired behavior.

Resources