Need a toggle menu that closes when it loses focus - wpf

This is going to be difficult to explain but bear with me.
I'm creating an application in WPF using the MVVM pattern. I'm new to it which is why I'm asking this question.
I have the application set up as 3 pages or views within a window. One of these is static and is always there, the other two are a couple of small settings pages that open in the top corner over the top of everything using zindex. At the moment the menu that opens these pages uses a listbox with togglebuttons as it's template (the checked state is bound to the listbox) so that you can click to open the menu, then click the button again to close it.
In an ideal world I'd like it so that if the menu page were to lose focus (listen for a click on the static view?) the settings views close too. Also I wondered if anyone had a simpler solution for a menu that works in a similar way because at the moment it is a pretty messy solution. Here are some code samples:
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding PageViewModels}" SelectedItem="{Binding CurrentPageViewModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="10,0" Text="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FCCC"/>
<ToggleButton
VerticalAlignment="Stretch"
Content=""
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Settings views -->
<ContentControl Panel.ZIndex="2" Grid.Row="1" Grid.Column="0" Content="{Binding CurrentPageViewModel}"/>
<!-- Main page view -->
<ContentControl Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Width="1000" Height="700" Content="{Binding StaticPageViewModel}"/>
I'm using the concepts in this blog post to manage my views and viewmodels, however I changed the way the menu is shown so I could remove the need for a change page command/ICommand.
TL;DR : I'm looking for suggestions and criticism with what I could do to improve the way I've currently created my menu bar.

I would create an attached property if you really wanna do the MVVM style to close the View when it loses focus.
public static readonly DependencyProperty CloseViewOnLostFocusProperty =
DependencyProperty.RegisterAttached("CloseViewOnLostFocus", typeof (object), typeof (MainWindow), new FrameworkPropertyMetadata(default(object), RegisterLostFocus));
private static void RegisterLostFocus(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
//This is the control that is attached to it e.g. ContentControl
var sender = dependencyObject as FrameworkElement;
//Your ViewModel
var viewModel = dependencyPropertyChangedEventArgs.NewValue as ViewModel;
if (sender != null)
{
sender.LostFocus += (o, args) =>
{
//Close whatever you are doing right now to close the View.
viewModel.Close();
};
}
}
And on your view you can attach whatever ViewModel you want to close when it loses focus e.g. your SettingsView got LostFocus it'll close that view. In here I created an attached property on my MainWindow class.
<!-- Settings views -->
<ContentControl
MainWindow.CloseViewOnLostFocus="{Binding RelativeSource={RelativeSource Self},Path=Content}"
x:Name="SettingsView" Panel.ZIndex="2" Grid.Row="1" Grid.Column="0" Content="{Binding CurrentPageViewModel}"/>
<!-- Main page view -->
<ContentControl x:Name="MainPageView" Grid.Row="1" Grid.RowSpan="2" Grid.ColumnSpan="2" Width="1000" Height="700" Content="{Binding StaticPageViewModel}"/>

You can have a ContextMenu attached to the button, which is opened when the button is clicked. This way, the close-when-unfocused behaviour is entirely automatic.
You can then just restyle the menu to look however you want.

Related

Improve performance for huge ListBox in StackPanel?

I am using a StackPanel to layout several controls vertically (ie, Title, sub titles, listbox, separator, listbox, etc).
The StackPanel is a child of a ScrollViewer to ensure its content is always scrollable.
One of the controls in the StackPanel is a ListBox.
Its ItemsSource is data bound to a huge collection, and a complex DataTemplate is used to realise each item.
Unfortunately, I'm getting really poor performance (high cpu/memory) with it.
I tried
setting the ListBox's ItemsPanel to a VirtualizingStackPanel, and
overriding its ControlTemplate to only an ItemsPresenter (remove the ListBox's ScrollViewer).
But there were no difference in performances. I'm guessing the StackPanel gives its internal children infinite height during measure?
When I replaced the ScrollViewer and StackPanel with other panels/layouts (e.g, Grid, DockPanel) and the performance improves significantly, which leads me to believe the bottleneck, as well as solution, is in virtualization.
Is there any way for me to improve the cpu/memory performance of this view?
[Update 1]
Original Sample project: http://s000.tinyupload.com/index.php?file_id=29810707815310047536
[Update 2]
I tried restyling/templating TreeView/TreeViewItems to come up with the following example. It still takes a long time to start/same,high memory usage. But once loaded, scrolling feels a lot more responsive than the original sample.
Wonder if there's any other way to further improve the start up time/memory usage?
Restyled TreeView project: http://s000.tinyupload.com/index.php?file_id=00117351345725628185
[Update 2]
pushpraj's solution works like a charm
Original:
Startup: 35s,
Memory: 393MB
Scrolling: Slow
TreeView:
Startup: 18s,
Memory 377MB,
Scrolling: Fast
pushpraj's solution:
Startup: <1s,
Memory: 20MB,
Scrolling: Fast
you may perhaps limit the maximum size of the huge list box and enable Virtualization
eg
<ListBox MaxHeight="500"
VirtualizingPanel.IsVirtualizing="true"
VirtualizingPanel.VirtualizationMode="Recycling" />
this will enable the ListBox to load a few items only and will enable a scrollbar on listbox to scroll to rest of the items if needed.
at the same time setting VirtualizationMode to Recycling will help you to reuse the complex data templates thus eliminating the need of re creating them again for every item.
EDIT
here is a solution based on your sample, I have used CompositeCollection with Virtualization to achieve the desired.
xaml
<Grid xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:l="clr-namespace:PerfTest">
<Grid.Resources>
<DataTemplate DataType="{x:Type l:Permission}">
<StackPanel Orientation="Horizontal">
<CheckBox />
<TextBlock Text="{Binding Name}" />
<Button Content="+" />
<Button Content="-" />
<Button Content="..." />
</StackPanel>
</DataTemplate>
<CompositeCollection x:Key="data">
<!-- Content 1 -->
<TextBlock Text="Title"
FontSize="24"
FontWeight="Thin" />
<!-- Content 2 -->
<TextBlock Text="Subtitle"
FontSize="16"
FontWeight="Thin" />
<!-- Content 3 -->
<CollectionContainer Collection="{Binding DataContext, Source={x:Reference listbox}}" />
<!-- Content 4 -->
<TextBlock Text="User must scroll past the entire list box before seeing this"
FontSize="16"
FontWeight="Thin"
Padding="5"
TextWrapping="Wrap"
Background="#99000000"
Foreground="White" />
</CompositeCollection>
</Grid.Resources>
<ListBox x:Name="listbox"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{StaticResource data}" />
</Grid>
code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var items = new ObservableCollection<Permission>();
foreach (var i in Enumerable.Range(0, 10000).Select(i => new Permission() { Name = "Permission " + i }))
{ items.Add(i); }
DataContext = items;
}
}
public class Permission
{
public string Name { get; set; }
}
since we can not create data template for string so I changed the string collection to Permission collection. I hope in your real project it would be something similar.
give this a try and see if this is close to what you need.
note: you may safely ignore if there is any designer warning on Collection="{Binding DataContext, Source={x:Reference listbox}}"

RadTabControl reloading content in swiching tabitems

I'm using RadTabControl and I have a problem with reloading tabs. If I add two tabs and edit first tab content and go to the second tab and come back first tab lost the content. If I edit some tab content and click to edited tab title and go to another tab and come back it binds and changes context. I have a lost focus event for textbox(content) if I set break point to lost focus event and after running I test all situation it's good working but I need to change Note.Content property in lost focus by binding Content textbox with content property. Content property is in Note class.
xmlns:ec="clr-namespace:WpfControls;assembly=WpfControls"
<UserControl.Resources>
<DataTemplate x:Key="TabContent">
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding Content}" Name="ContentTextBox" MinLines="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextWrapping="Wrap" AcceptsReturn="True"/>
</ScrollViewer>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid Grid.Column="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ec:NoteBook x:Name="uiNotebook" TabContentTemplate="{StaticResource TabContent}" Margin="30" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Grid>
public DataTemplate TabContentTemplate
{
get { return (DataTemplate)GetValue(TabContentTemplateProperty); }
set { SetValue(TabContentTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for ContentTemplate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TabContentTemplateProperty =
DependencyProperty.Register("TabTemplate", typeof(DataTemplate), typeof(NoteBook), new PropertyMetadata(null));
Can anybody help me? Thanks Jamshed
After long researches I find the answer and I want to share it. it was IsContentPreserved property and I set it to true, it works!
<telerik:RadTabControl x:Name="tabControl" IsContentPreserved="True" ItemsSource="{Binding Tabs}">

How to use up and down key to move treeviewitem selection in WPF

We create a HierarchicalDataTemplate for a treeview control. We can use mouse to click the tree item to change the selection. Now, we want to use keyboard up and down key to move the selection up and down. But it seems that it can't work. I searched a lot by Google and Stackoverflow, but failed.
So I created a new thread for this, could you please give me some help? thx.
<HierarchicalDataTemplate x:Uid="HierarchicalDataTemplate_1" x:Key="My_data_template" >
<ContentControl x:Uid="ContentControl_1" MouseDoubleClick="MouseDoubleClick" MouseRightButtonDown="MouseRightClick">
<Grid x:Uid="Grid_2" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Uid="ColumnDefinition_1" Width="*"/>
<ColumnDefinition x:Uid="ColumnDefinition_2" Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel x:Uid="StackPanel_3" HorizontalAlignment="Left" Orientation="Horizontal" Grid.Column="0">
<TextBlock x:Uid="TextBlock_13" Text="{Binding Name}" VerticalAlignment="Center" Margin="3,0,0,1" TextWrapping="NoWrap"/>
</StackPanel>
<CheckBox x:Uid="CheckBox_3" HorizontalAlignment="Right" Click="CheckBox_Click" Grid.Column="1" ToolTip="On/Off">
</CheckBox>
</Grid>
</ContentControl>
</HierarchicalDataTemplate>
Another question is that, I can use mouse to click the textblock to select the item, but when I use mouse click the CheckBox, the item can't be selected. Is there anyway to make treeview item selected when I click the CheckBox?
The way I applied the template to treeview is as following:
<TreeView x:Name="tv_pointcloud" x:Uid="TreeListView_1"
ItemTemplateSelector="{StaticResource DataAccessor}"
......
/>
public class DataAccessor : DataTemplateSelector
{
public DataAccessor()
{
Init();
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var element = container as FrameworkElement;
var template = element.FindResource("My_data_template") as DataTemplate;
return template;
}
......
}
thanks.
I had the same problem as you, in a WPF treeview I was unable to use Arrow Keys to navigate. The problem I found was the Checkbox that was getting the focus. So I set "Focusable = False for the checkbox, and now the navigation in the treeview is as expected:
<CheckBox Focusable="False" ... />
Keyboard commands and such are called gestures. Perhaps this is a good place to get you started:
Keyboard shortcuts in WPF

Button and ToggleButton in wpf Toolbar using MVVM

I have a wpf Toolbar in a ToolbarTray inside my application which must host Buttons and ToggleButtons.
Can someone suggest me how to implement this behavior in MVVM?
The code below is what I have right now:
<ToolBarTray Margin="5,30,5,30" MinWidth="35" HorizontalAlignment="Center" Orientation="Vertical" Background="Transparent">
<ToolBar x:Name="ToolBarControl" HorizontalAlignment="Stretch" ItemsSource="{Binding ToolBarItems}" >
<ToolBar.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" HorizontalAlignment="Stretch" Margin="0,0,0,15"
Template="{Binding ToolBarIcon}"
cal:Message.Attach="[Event Click] = [Action Select()]"
IsEnabled="True"
ToolTip="{Binding Text}"/>
</DataTemplate>
</ToolBar.ItemTemplate>
</ToolBar>
</ToolBarTray>
Where the Button could be a normal Button or a ToggleButton.
Thanks in Advance.
In MVVM pattern, your model class will contain the properties (all data objects) which you want to bind and display in view. So ToolBarItems collection will be part of model.
Your view basically will contain the above code you have written. And in code behind file, there will be an object of type model class as a property.
Your viewmodel can initialize the model and view objects and bind the model to view's datacontext.

Cannot access drag adorner template

I've used the sample code provided by Bea Stollnitz (http://bea.stollnitz.com/blog/?p=53), in order to enable drag and drop in my application, and drag adorner, etc.
Everything works fine, my drag adorner is well displayed, I have all the behavior I want.
But (yes there is always a but), I cannot access the DataTemplate of the Drag Adorner, in order to display different data depending on the dragged data.
I have simplified the code, but the basics are still there.
This is the DataTemplate of my DragAdorner
<DataTemplate x:Key="DragAndDropTemplate" DataType="{x:Type MyType}">
<Grid>
<Grid Opacity="0.5">
<Border x:Name="HeaderBorder" CornerRadius="2" BorderThickness="1" Margin="5,2,5,2">
<Border x:Name="InsideBorder" CornerRadius="2" BorderThickness="1">
<TextBlock x:Name="number" Text="{Binding Name}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
</Border>
</Border>
</Grid>
<Border Width="17" Height="17" BorderBrush="White" HorizontalAlignment="Center" VerticalAlignment="Center" CornerRadius="1" x:Name="numberContainer" Visibility="Collapsed">
<TextBlock x:Name="number" Text="80" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
</Border>
</Grid>
</DataTemplate>
This is the code that create the Adorner :
if (this.draggedAdorner == null)
{
var adornerLayer = AdornerLayer.GetAdornerLayer(source);
this.draggedAdorner = new DraggedAdorner(draggedData, dataTemplate, source, adornerLayer);
}
And this is the code that init an adorner
public DraggedAdorner(List dragDropData, DataTemplate dragDropTemplate, FrameworkElement adornedElement, AdornerLayer adornerLayer)
: base(adornedElement)
{
this.adornerLayer = adornerLayer;
this.contentPresenter = new ContentPresenter();
this.contentPresenter.Content = dragDropData[0];
this.contentPresenter.ContentTemplate = dragDropTemplate;
this.adornerLayer.Add(this);
}
The draggedData, will be a list of MyType, I get the first item as the content of the ContentPresenter of my DraggedAdorner, so the DataTemplate can apply.
The problem is, I want to access the numberContainer and number control of the DataTemplate, in order to display the number of dragged object, in the adorner. But I cannot manage to access it, whatever I try, It ends with the "This operation is valid only on elements that have this template applied." message.
I have tought I could do something like this :
this.contentPresenter.ContentTemplate.FindName("number", this.contentPresenter);
Since the DataTemplate should apply to the ContentPresenter, but nope...
For information the adornedElement is the ListViewItem from which the drag occurs.
If you have any idea...
Ok, so I have found how to achieve what I wanted.
I don't know why it didn't comes to mind earlier, and why I didn't found anything about this before.
I have just added a single line before trying to access the template :
this.UpdateLayout()
Looks like it forces the ContentPresenter and DataTemplate object to be update and "re-rederend" so the ContentPresenter is really templated by my DataTemplate.

Resources