WPF ContextMenu + MenuItem memory leak - c#

Can anybody help with WPF and how-to free ContextMenu + MenuItem. As I’m using it, it keeps hold a WeakReference.
I have created a demo application, where it adds 10000 UserControl to a StackPanel and then removes it again. If the UserControl ContextMenu has no MenuItem, everything is fine. But if it contains a
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Properties" />
</ContextMenu>
</UserControl.ContextMenu>
Then Teleriks profiler reports 180.050 instances of WeakReference. How can I free this up?
I have googled without luck - I have read the following pages, but couldn't see any solution, else I have missed some of the informations.
Strange wpf memory leak
WPF Combobox "leaks" memory
https://social.msdn.microsoft.com/Forums/vstudio/en-US/aa987282-6c43-4652-b712-2397d4044ff5/memoryleak-with-contextmenu-and-menuitem?forum=wpf
Demo application:
MainWindow.xaml has added:
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Add Empties" Click="AddClick" Margin="3" />
<Button Content="Add With ContextMenu" Click="AddWithContextMenuClick" Margin="3" />
<Button Content="Remove" Click="RemoveClick" Margin="3" />
</StackPanel>
<StackPanel x:Name="PagesStackPanel">
</StackPanel>
</StackPanel>
MainWindow.xaml.cs has added:
private void AddClick(object sender, RoutedEventArgs e)
{
for (var i = 0; i < 10000; i++)
{
PagesStackPanel.Children.Add(new ChildViewEmpty());
}
}
private void RemoveClick(object sender, RoutedEventArgs e)
{
PagesStackPanel.Children.Clear();
}
private void AddWithContextMenuClick(object sender, RoutedEventArgs e)
{
for (var i = 0; i < 10000; i++)
{
PagesStackPanel.Children.Add(new ChildViewWithMenu());
}
}
ChildViewEmpty.xaml has added:
<Grid />
ChildViewWithMenu.xaml has added:
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Properties" />
</ContextMenu>
</UserControl.ContextMenu>
Telerik profiler dump, where we can see the memory I can't free with GC, when ContextMenu has a MenuItem.
Telerik profiler dump, where ContextMenu has no MenuItem.
I’m running this on Windows 8.1 with .NET 4.5

Related

Proper way of specifying DataContext (WPF)

I just started working with WPF. In my new application I implemented notify icon with context menu first. Next I started building MVVM framework and found that the new changes impact the code already implemented.
I am using NotifyIcon from Hardcodet. My initial version was something like this:
<Window x:Class="ScanManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:commands="clr-namespace:ScanManager.Commands"
Title="Scan" Height="542" Width="821">
<Grid Visibility="Visible" Loaded="form_Loaded">
...
<tb:TaskbarIcon HorizontalAlignment="Left" Margin="357,537,0,0" Name="mainTaskbarIcon" VerticalAlignment="Top" IconSource="/Icons/TestIcon.ico" IsHitTestVisible="True" ToolTipText="Test Test" >
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="_Show" Command="{commands:ShowMainWindowCommand}" CommandParameter="{Binding}" />
<MenuItem Header="_Hide" Command="{commands:HideMainWindowCommand}" CommandParameter="{Binding}" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
<Button Name="hideButton" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click" />
</Grid>
</Window>
Next I started incorporating MVVM pattern based on article The World's Simplest C# WPF MVVM Example. The example project adds DataContext pointing to a ViewModel class.
<Window.DataContext>
<ViewModels:Presenter/>
</Window.DataContext>
This change affected the way the notification icon works. In a nutshell, the overriding methods ICommand.CanExecute(object parameter) and ICommand.Execute(object parameter) of the ShowMainWindowCommand and HideMainWindowCommand objects started receiving object Presenter defined in Window.DataContext instead of original Hardcodet.Wpf.TaskbarNotification.TaskbarIcon. And I am guessing this is because the added DataContext affects the {Binding} value of the CommandParameter.
The Execute method expects the parameter to be TaskbarIcon in order to identify the parent Window object, which then can be set shown or hidden.
The way I was trying to address it I moved all elements but the TaskbarIcon from Window to a UserControl, under a Grid and applied DataContext to the Grid
<UserControl x:Class="ScanManager.Views.SControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...
d:DataContext="{d:DesignInstance ViewModels:Presenter}">
<Grid Visibility="Visible">
<Grid.DataContext>
<ViewModels:Presenter/>
</Grid.DataContext>
<Button Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click" />
...
</Grid>
</UserControl>
It addressed the issue with notify icon, but I am wondering if this is right way of resolving the situation. I thought the other way could be to set CommandParameter in MenuItem in the original version after DataContext was added, to proper value, however I am having hard time figuring this out.
As the next step, I am trying to cast DataContext of the UserControl object to INotifyPropertyChanged in order to subscribe to PropertyChanged event, however the DataContext property comes in as null, presumable because it was set only to Grid and not to the UserControl:
INotifyPropertyChanged viewModel = (INotifyPropertyChanged)this.DataContext;
Any guidance on putting these pieces together properly would be much appreciated.
Edit
Access Denied, these options are helpful for the Button element.
What if I would like to come back to the initial version at the top, the MenuItem element uses Command="{commands:ShowMainWindowCommand}" and CommandParameter="{Binding}". If I add Window.DataContext, is there a change that can be done to the MenuItem's Command/CommandParameter attributes in order to reference what they referred before (I assume, the parent element)? I tried CommandParameter="{Binding Path=mainTaskbarIcon}" and it did not work meaning, like before, the Execute/CanExecute receive null.
<Window x:Class="ScanManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:commands="clr-namespace:ScanManager.Commands"
Title="Scan" Height="542" Width="821">
<Window.DataContext>
<ViewModels:Presenter/>
</Window.DataContext>
<Grid Visibility="Visible" Loaded="form_Loaded">
...
<tb:TaskbarIcon HorizontalAlignment="Left" Margin="357,537,0,0" Name="mainTaskbarIcon" VerticalAlignment="Top" IconSource="/Icons/TestIcon.ico" IsHitTestVisible="True" ToolTipText="Test Test" >
<tb:TaskbarIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="_Show" Command="{commands:ShowMainWindowCommand}" CommandParameter="{Binding Path=mainTaskbarIcon}" />
<MenuItem Header="_Hide" Command="{commands:HideMainWindowCommand}" CommandParameter="{Binding Path=mainTaskbarIcon}" />
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
...
</Grid>
</Window>
When you set datacontext it spreads to the inner controls as well and yes it affects Binding context. There is no need to create UserControl since it does not prevent context from spreading. In order to prevent it change datacontext of the control or specify binding source. For example, if you want to change context of the button.
Approach with DataContext override:
<Grid Visibility="Visible">
<Grid.Resources>
<ViewModels:Presenter x:Key="buttonContext"/>
</Grid.Resources>
<Button DataContext="{StaticResource buttonContext}" Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
Approach with specifying source:
<Grid.Resources>
<ViewModels:Presenter x:Key="buttonContext"/>
</Grid.Resources>
<Button Name="hideButton" Command="{Binding Source={StaticResource buttonContext}, Path=HideMainWindowCommand}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
Or you can also have ButtonContext property in your root viewModel and resolve it this way:
<Button DataContext="{Binding ButtonContext}" Name="hideButton" Command="{Binding Path=HideMainWindowCommand}" CommandParameter="{Binding}" Content="Hide window" Height="23" HorizontalAlignment="Right" Margin="0,408,50,0" VerticalAlignment="Top" Width="92" IsEnabled="True" Click="hideButton_Click"/>
How to subscribe to DataContextChanged event:
public MainWindow()
{
InitializeComponent();
DataContextChanged += MainWindow_DataContextChanged;
Handle event:
private void MainWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null && e.OldValue is INotifyPropertyChanged)
{
((INotifyPropertyChanged)e.OldValue).PropertyChanged -= MainWindow_PropertyChanged;
}
if (e.NewValue != null && e.NewValue is INotifyPropertyChanged)
{
((INotifyPropertyChanged)e.NewValue).PropertyChanged += MainWindow_PropertyChanged;
}
}
private void MainWindow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
You don't have to adhere to Commanding. Change your menu items to use a click event instead, and they can call command operation from the View's codebehind.
Add Click="{Your click event name}"
F12 on the click event to create/go to the event.
As an aside here is a way to bind a VM to a data contect without doing it in the XAML.
Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding

Listview ContextMenu Unclickable

So I have a contextmenu in my xaml code which is
<ListView x:Name="listView" Grid.Row="1" SelectionChanged="listView_SelectionChanged" IsEnabled="True">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Command="Delete" IsEnabled="True" Click="MenuItem_Click"></MenuItem>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
and then in my c# code I have
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
listView.Items.Remove(listView.SelectedItems[0]);
showList.RemoveAt(listView.Items.IndexOf(listView.SelectedItems[0]));
}
but unfortunatelly when I debug my program It won't let me click contextmenu delete button :( How can I solve this?

Accessing a button created in a sub xaml page from it's parent Shell xaml

I'm working with a SplitView application, and I've created a Shell that hosts the subpages. What I'd like to do is have a button on a subpage that when clicked, can manipulate the visibility of objects originally initiated or created on the Shell xaml. But, I'm not sure how to call the class or frame necessary to change attributes on the SplitView.
My hierarchy is as follows: Shell > Pages Folder > Sub Pages
Here's the snippet from my Shell.xaml Mind you this is only a portion contained within the SplitView
<SplitView.Pane>
<StackPanel x:Name="SplitViewPanePanel">
<RadioButton x:Name="BackRadioButton" Click="BackRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Background="Gray" Content="Back" GroupName="Back"/>
<RadioButton x:Name="HamburgerRadioButton"
Click="HamburgerRadioButton_Click"
Style="{StaticResource NavRadioButtonStyle}"
Tag=""
Content="Menu" />
<RadioButton x:Name="HomeRadioButton" Click="HomeRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Home" GroupName="Navigation"/>
<RadioButton x:Name="TARadioButton" Click="MTRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Team Assignments" GroupName="Navigation"/>
<RadioButton x:Name="ASRRadioButton" Click="ASRRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Assignment Switch Requests" GroupName="Navigation"/>
<RadioButton x:Name="MTRadioButton" Click="MTRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Management Tools" GroupName="Navigation"/>
</StackPanel>
</SplitView.Pane>
Below is the code from my "Home" page inside the Pages folder.
using Team_Management_Hub;
using Team_Management_Hub.Pages;
namespace Team_Management_Hub.Pages
{
public sealed partial class Home : Page
{
public Home()
{
this.InitializeComponent();
}
private void TestButton_Click(object sender, RoutedEventArgs e)
{
//Here is the button I want to be able to click to alter the visibility of the MTRadioButton
}
}
}
I've looked for a couple days now for examples or explanations on how to access the parent Shell, and I can't seem to figure it out. It should be noted that this is my first UWP in Windows 10, and I'm fairly new to C#.
Additionally, if it helps or is relevant, the below code is inside the Shell.xaml.cs file that I use to navigate to individual Pages.
private void HomeRadioButton_Click(object sender, RoutedEventArgs e)
{
var frame = this.DataContext as Frame;
Page page = frame?.Content as Page;
if (page?.GetType() != typeof(Home))
{
frame.Navigate(typeof(Home));
}
}
UPDATE:
So, I've implemented some changes from the first answer provided, and I seem to have something incorrect with my scope inside the Shell.xaml file.
Here's my Shell.xaml file:
<Page
x:Class="Team_Management_Hub.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Team_Management_Hub"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="using:Team_Management_Hub.Pages"
mc:Ignorable="d">
<pages:Home MyCoolEvent="OnCoolEvent"/>;
<SplitView x:Name="Team_Management_Hub" Background="Black" OpenPaneLength="240" CompactPaneLength="48"
DisplayMode="CompactOverlay" IsPaneOpen="False" PaneBackground="Gray" Content="{Binding}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="HardwareButtons">
<VisualState.Setters>
<Setter Target="BackRadioButton.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<SplitView.Pane>
<StackPanel x:Name="SplitViewPanePanel">
<RadioButton x:Name="BackRadioButton" Click="BackRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Background="Gray" Content="Back" GroupName="Back"/>
<RadioButton x:Name="HamburgerRadioButton" Click="HamburgerRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Menu" />
<RadioButton x:Name="HomeRadioButton" Click="HomeRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Home" GroupName="Navigation"/>
<RadioButton x:Name="TARadioButton" Click="TARadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Team Assignments" GroupName="Navigation"/>
<RadioButton x:Name="ASRRadioButton" Click="ASRRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Assignment Switch Requests" GroupName="Navigation"/>
<RadioButton x:Name="MTRadioButton" Click="MTRadioButton_Click" Style="{StaticResource NavRadioButtonStyle}" Tag="" Content="Management Tools" GroupName="Navigation"/>
</StackPanel>
</SplitView.Pane>
</SplitView>
With this, everything under <pages:Home MyCoolEvent="OnCoolEvent"/> gets underlined blue, and the error "the property Content is set more than once"
I found this link and so I tried to implement putting it all in a grid, but when I simply add <Grid> above the <SplitView> and below the final </SplitView> if I keep the <pages> portion above the splitview, the button on the Home.xaml page doesn't do anything. If I move the <pages> to below the <SplitView> but still inside the <Grid></Grid> the button works, but the SplitView disappears. I also tried throwing the <pages> inside the <StackPanel> that holds my RadioButtons, but it does something weird, where it appears my Home Page is inside the SplitView Pane under the buttons. So, I'm sure I've got some simple mistake I'm making with the scope of the xaml setup, but I've tried so many combinations of placing the <pages:Home> in so many different areas and inside different controls, and I haven't gotten it to work. So, some help on the proper layout to make this work, would be great.
Thanks for any help.
A very simply approach would be to add an event to your Home class. When the TestButton is clicked fire the event. You would then listen for the event on your "shell" page.
example shell xaml:
<pages:Home MyCoolEvent="OnCoolEvent"/>
example shell code
private void OnCoolEvent(object sender, EventArgs args)
{
//alter the visibility of the MTRadioButton
}
Home code:
public event EventHandler MyCoolEvent;
private void TestButton_Click(object sender, RoutedEventArgs e)
{
OnMyCoolEvent();
}
protected virtual void OnMyCoolEvent()
{
EventHandler handler = MyCoolEvent;
if (handler != null) handler(this, EventArgs.Empty);
}

How to i add an Item Click event to an items control

I am having problems with generating a click event for items inside an items control. I am new to xaml nad wpf. Can you the experts help me out. Below is the code that i have come up with but now having no clue on how to add a click event to for the generated items. Will very much appreciate your responses. Thank you for reading
<ItemsControl ItemsSource="{Binding Text, Source={StaticResource TextContainer}}">
<!--text is an object bein made public from TextToDisplay. There can be many objects released ratger than one in this case-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel ItemWidth="100"
ItemHeight="100" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type sys:String}">
<Border CornerRadius="100"
Background="BlueViolet">
<Button Margin="20"
Content="{Binding}"
HorizontalContentAlignment="Center" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
probably the easiest thing you can do is create your own user control... make it a custom button and add click event to that control so everytime the listbox add your control it will already have a click_event built in.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
// add your code here.
}
then for the listbox do something like that...
private void AddItemsButton_Click(object sender, RoutedEventArgs e)
{
ListBoxTest.Items.Add(new UserControl1());
}
this adds a usercontrol button to the listbox everytime you click on a button titled "add items"

how to handle TabItem single click event in WPF?

In my application I have used WPF TabControl I want to handle click event of the TabItem.
How do i achieve it?
You can do this by adding labels to the header property for each tabitem in the tabcontrol. Then you can set an event for the label.
xaml
<TabControl Height="100" HorizontalAlignment="Left" Name="tabControl1">
<TabItem Name="tabItem1">
<TabItem.Header>
<Label Content="tabItem1"
MouseLeftButtonDown="tabItem1_Clicked"
HorizontalAlignment="Stretch"/>
</TabItem.Header>
<Grid />
</TabItem>
<TabItem Name="tabItem2">
<TabItem.Header>
<Label Content="tabItem2"
MouseLeftButtonDown="tabItem2_Clicked"
HorizontalAlignment="Stretch"/>
</TabItem.Header>
<Grid />
</TabItem>
</TabControl>
C# / Code Behind
private void tabItem1_Clicked(object sender, MouseButtonEventArgs e)
{
//DO SOMETHING
}
private void tabItem2_Clicked(object sender, MouseButtonEventArgs e)
{
//DO SOMETHING
}
Hope this helps.
This is an old question but I find an answer after being in the same situation.
I use SelectionChanged event on the TabControl (XAML)
<TabControl SelectionChanged="TabControl_SelectionChanged">
Code behind (C#):
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Stuff
}
This is not the click HimSelf but its working for refreshing stuff..
You can use SelectionChanged event in TabControl and use switch case to do anything you like.
// XAML Code
<TabControl SelectionChanged="TabControl_SelectionChanged">
<TabItem Header="Item1"></TabItem>
<TabItem Header="Item2"></TabItem>
<TabItem Header="Item3"></TabItem>
</TabControl>
// Behind Code
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string tabItem = ((sender as TabControl).SelectedItem as TabItem).Header as string;
switch(tabItem)
{
case "Item1":
break;
case "Item2":
break;
case "Item3":
break;
default:
return;
}
}
Note that, although the answer provided by d.moncada and others addresses the question, the question itself might not be what is intended. Knowing when the user clicks on a tab is different from knowing when the user makes a tab frontmost - it can be done by clicking on a tab, but it can also be achieved by other means. For example, if you click in the tab that is already frontmost, then use the left/right arrows, you can bring another tab to the front without mouse clicking - the mouse-left-button-down event does not get called in this case (as you would expect).
Wrap the header in a no-template button.
If you use the ItemsSource:
<TabControl ItemsSource="{Binding Data}">
<TabControl.ItemTemplate>
<DataTemplate>
<Button Click="Tab_Click">
<Button.Template>
<ControlTemplate>
<ContentPresenter />
</ControlTemplate>
</Button.Template>
<Button.Content>
<!-- Actual header goes here -->
</Button.Content>
</Button>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
If you have static content you can insert it into the header right away:
<TabControl>
<TabItem>
<TabItem.Header>
<Button Click="Tab_Click">
<Button.Template>
<ControlTemplate>
<ContentPresenter />
</ControlTemplate>
</Button.Template>
<Button.Content>
<!-- Actual header goes here -->
</Button.Content>
</Button>
</TabItem.Header>
</TabItem>
</TabControl>
You can do this by adding labels to the header property for each
tabitem in the tabcontrol. Then you can set an event for the label.
The solution works pretty well; however, the "label" has margin properties which can prevent the "MouseLeftButtonDown" handler from being fired unless the user clicks the label dead on. Additionally, it looks like the styling for the rest of the tabs are affected due to label padding.
You could alleviate this by overriding the default margin/padding properties of the label...or even more simply, using TextBlock.
<TabItem x:Name="tabItem1" >
<TabItem.Header>
<TextBlock MouseLeftButtonDown="tabItem1_Click">
Click Me
</TextBlock>
</TabItem.Header>
...
</TabItem>
try to find solution with GotFocus event.
private void addTabPage_GotFocus(object sender, RoutedEventArgs e)
{
addTabPage(); //method for adding page
TabControlPages.SelectedIndex = TabControlPages.Items.Count - 1; //select added page
TabControlPages.Focus(); //change forcus to selected page
}
also method for adding page (just example)
private void addTabPage()
{
TabItem tc = new TabItem();
tc.Header = "New page";
TabControlPages.Items.Insert(TabControlPages.Items.Count - 1, tc); //insert new page
}
hope this will be helpfull

Categories