So when the program starts and I right-click the ListViewItem the context menu is shown but its DataContext is not the object linked to the ListViewItem that I clicked. That is why this binding does not work and the converter's Convert method not fired:
<Image>
<Image.Source>
<Binding
Path="Process" Converter="{StaticResource PriorityToIconConverter}"
ConverterParameter="{StaticResource RealTime}" />
</Image.Source>
</Image>
But when I close the ContextMenu and open it again suddenly eerything works fine. But why not right after the start of the program?
I have this ListView:
<ListView Grid.Row="1" x:Name="ListView" Margin="0,0,0,10"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding ProcessInfos}"
Background="#1a1a1a" Foreground="#e0e0e0"
BorderBrush="#333333" BorderThickness="1"
SelectionMode="Single"
SelectedItem="{Binding SelectedProcessInfo, UpdateSourceTrigger=PropertyChanged}"
FontSize="15">
<ListView.Resources>
<ControlTemplate x:Key="CustomHeader" TargetType="GridViewColumnHeader">
<Grid MinWidth="50" Background="{TemplateBinding Background}" Height="40">
<TextBlock Background="{TemplateBinding Background}" TextAlignment="Center"
Foreground="{TemplateBinding Foreground}"
Text="{TemplateBinding Tag}" VerticalAlignment="Center" />
<Thumb x:Name="PART_HeaderGripper" HorizontalAlignment="Right" Margin="0" Width="1" />
</Grid>
</ControlTemplate>
<Style TargetType="GridViewColumnHeader">
<Style.Setters>
<Setter Property="Background" Value="#2b2b2b" />
<Setter Property="Foreground" Value="#e0e0e0" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="BorderBrush" Value="#333333" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Padding" Value="5" />
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#3a3a3a" />
<Setter Property="Foreground" Value="#ffffff" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#4a4a4a" />
<Setter Property="Foreground" Value="#e0e0e0" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.View>
<GridView d:DataContext="{d:DesignInstance Type=models:ProcessInfo}">
<GridViewColumn Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border>
<Image Source="{Binding Icon}" Width="20" Height="20" />
</Border>
<TextBlock Grid.Column="1" Text="{Bindin gProcess.ProcessName}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumn.Header>
<GridViewColumnHeader Template="{StaticResource CustomHeader}" Margin="-3,0,0,0"
BorderThickness="0" Tag="Process Name" />
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="100"
DisplayMemberBinding="{Binding Process.Id}">
<GridViewColumn.Header>
<GridViewColumnHeader Template="{StaticResource CustomHeader}" Margin="-3,0,0,0"
BorderThickness="0" Tag="PID" />
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="180"
DisplayMemberBinding="{Binding MemoryUsage}">
<GridViewColumn.Header>
<GridViewColumnHeader Template="{StaticResource CustomHeader}" Margin="-3,0,0,0"
BorderThickness="0" Tag="Memory Usage" />
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="180"
DisplayMemberBinding="{Binding CpuUsage}">
<GridViewColumn.Header>
<GridViewColumnHeader Template="{StaticResource CustomHeader}" Margin="-3,0,0,0"
BorderThickness="0" Tag="CPU Usage" />
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="120"
DisplayMemberBinding="{Binding Process.Threads.Count}">
<GridViewColumn.Header>
<GridViewColumnHeader Template="{StaticResource CustomHeader}" Margin="-3,0,0,0"
BorderThickness="0" Tag="Thread Count" />
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
And this ContextMenu of the ListViewItem:
<ContextMenu x:Key="MenuItemContextMenu" Opened="ListViewItem_ContextMenuOpening" Background="#1e1e1e" Foreground="#e0e0e0" BorderBrush="#4a4a4a"
d:DataContext="{d:DesignInstance models:ProcessInfo}">
<ContextMenu.Resources>
<ControlTemplate x:Key="MenuItemTemplate" TargetType="MenuItem">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="0" Padding="25 10">
<Grid>
<ContentPresenter ContentSource="Header" HorizontalAlignment="Left"
VerticalAlignment="Center" Margin="5,0" />
<ContentPresenter x:Name="Icon" ContentSource="Icon"
HorizontalAlignment="Right"
Width="5"
Height="5"
VerticalAlignment="Center" Margin="0 0 -10 0" />
<Popup x:Name="SubMenuPopup" Placement="Right"
IsOpen="{TemplateBinding IsSubmenuOpen}" Focusable="False"
PopupAnimation="Fade">
<Border Background="#1e1e1e" BorderBrush="#4a4a4a"
BorderThickness="1">
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" />
</Border>
</Popup>
</Grid>
</Border>
</ControlTemplate>
<!-- Style for MenuItems -->
<Style TargetType="MenuItem">
<Setter Property="Background" Value="#1e1e1e" />
<Setter Property="Foreground" Value="#e0e0e0" />
<Setter Property="Template" Value="{StaticResource MenuItemTemplate}">
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#333333" />
<Setter Property="Foreground" Value="#ffffff" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#444444" />
<Setter Property="Foreground" Value="#ffffff" />
</Trigger>
<Trigger Property="IsSubmenuOpen" Value="True">
<Setter Property="Background" Value="#333333" />
</Trigger>
</Style.Triggers>
</Style>
<diagnostics:ProcessPriorityClass x:Key="RealTime">RealTime</diagnostics:ProcessPriorityClass>
<diagnostics:ProcessPriorityClass x:Key="High">High</diagnostics:ProcessPriorityClass>
<diagnostics:ProcessPriorityClass x:Key="AboveNormal">AboveNormal</diagnostics:ProcessPriorityClass>
<diagnostics:ProcessPriorityClass x:Key="Normal">Normal</diagnostics:ProcessPriorityClass>
<diagnostics:ProcessPriorityClass x:Key="BelowNormal">BelowNormal</diagnostics:ProcessPriorityClass>
<diagnostics:ProcessPriorityClass x:Key="Low">Idle</diagnostics:ProcessPriorityClass>
<converters:PriorityToIconConverter x:Key="PriorityToIconConverter" />
</ContextMenu.Resources>
<!-- Set Affinity -->
<MenuItem Header="Set affinity" />
<!-- Set Priority with Submenu -->
<MenuItem Header="Set priority" x:Name="MenuItem">
<MenuItem Header="Realtime" CommandParameter="{StaticResource RealTime}">
<MenuItem.Icon>
<Image>
<Image.Source>
<Binding
Path="Process" Converter="{StaticResource PriorityToIconConverter}"
ConverterParameter="{StaticResource RealTime}" />
</Image.Source>
</Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="High" CommandParameter="{StaticResource High}">
<MenuItem.Icon>
<Image>
<Image.Source>
<Binding
Path="Process" Converter="{StaticResource PriorityToIconConverter}"
ConverterParameter="{StaticResource High}" />
</Image.Source>
</Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Above Normal" CommandParameter="{StaticResource AboveNormal}">
<MenuItem.Icon>
<Image>
<Image.Source>
<Binding
Path="Process" Converter="{StaticResource PriorityToIconConverter}"
ConverterParameter="{StaticResource AboveNormal}" />
</Image.Source>
</Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Normal" CommandParameter="{StaticResource Normal}">
<MenuItem.Icon>
<Image>
<Image.Source>
<Binding
Path="Process" Converter="{StaticResource PriorityToIconConverter}"
ConverterParameter="{StaticResource Normal}" />
</Image.Source>
</Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Below Normal" CommandParameter="{StaticResource BelowNormal}">
<MenuItem.Icon>
<Image>
<Image.Source>
<Binding
Path="Process" Converter="{StaticResource PriorityToIconConverter}"
ConverterParameter="{StaticResource BelowNormal}" />
</Image.Source>
</Image>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Low" CommandParameter="{StaticResource Low}">
<MenuItem.Icon>
<Image>
<Image.Source>
<Binding
Path="Process" Converter="{StaticResource PriorityToIconConverter}"
ConverterParameter="{StaticResource Low}" />
</Image.Source>
</Image>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</ContextMenu>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="#1e1e1e" />
<Setter Property="BorderBrush" Value="#333333" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="Padding" Value="10" />
<Setter Property="Margin" Value="0,0,0,5" />
<Setter Property="Foreground" Value="#e0e0e0" />
<Setter Property="ContextMenu" Value="{StaticResource MenuItemContextMenu}" />
<!-- Hover and Selected States -->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#333333" />
<Setter Property="Foreground" Value="#ffffff" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#444444" />
<Setter Property="Foreground" Value="#ffffff" />
</Trigger>
</Style.Triggers>
</Style>
ViewModel:
using System.Diagnostics;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using TaskManagerPlusPlus.Dialogs;
using TaskManagerPlusPlus.Models;
using TaskManagerPlusPlus.Services;
using TaskManagerPlusPlus.Views;
using Timer = System.Timers.Timer;
namespace TaskManagerPlusPlus.ViewModels;
public partial class TasksViewModel : BaseViewModel
{
private readonly IProcessInfosManager _processInfosManager;
[ObservableProperty] private ProcessInfosCollection _processInfos = [];
[ObservableProperty] private ProcessInfo? _selectedProcessInfo;
public TasksViewModel(IProcessInfosManager processInfosManager)
{
_processInfosManager = processInfosManager;
RefreshProcesses();
var timer = new Timer(1000)
{
AutoReset = true
};
timer.Elapsed += (_, _) => { RefreshProcesses(); };
timer.Start();
}
private void RefreshProcesses()
{
var selectedProcessInfoProcessId = SelectedProcessInfo?.Process.Id;
try
{
var processInfos = _processInfosManager.GetProcessInfos();
ProcessInfos.Clear();
foreach (var processInfo in processInfos)
{
ProcessInfos.Add(processInfo);
}
Application.Current.Dispatcher.Invoke(() =>
{
ProcessInfos.NotifyCollectionChanged();
SelectedProcessInfo = ProcessInfos.FirstOrDefault(processInfo =>
processInfo.Process.Id == selectedProcessInfoProcessId);
});
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
[RelayCommand]
private void KillProcess()
{
SelectedProcessInfo?.Process.Kill();
}
[RelayCommand]
private void StartProcess()
{
try
{
var startProcessDialog = new StartProcessDialog();
var showDialog = startProcessDialog.ShowDialog();
if (showDialog.HasValue && showDialog.Value)
{
var process = new Process
{
StartInfo = new ProcessStartInfo(startProcessDialog.TaskLocation)
{
ErrorDialog = true
}
};
process.Start();
}
}
catch (Exception)
{
MessageBox.Show("Process cannot be started.", "Process startup failure", MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
}
I have tried this:
public TasksView()
{
InitializeComponent();
var timer = new Timer(2000) { AutoReset = true };
timer.Elapsed += (_, _) =>
{
try
{
var listViewItem = (ListViewItem)ListView.ItemContainerGenerator.ContainerFromItem(ListView.Items[0]);
Dispatcher.Invoke(() =>
{
var contextMenu = listViewItem.ContextMenu!;
var menuItem = (MenuItem)((MenuItem)contextMenu.Items[1]!).Items[3]!;
Console.WriteLine(contextMenu.DataContext == null);
});
}
catch (Exception e)
{
Console.WriteLine(e);
}
};
timer.Start();
}
It showed that when the program started the data context is null but then after closing and opening again it shows that it is not null.
I also tried this:
private void TasksView_OnLoaded(object sender, RoutedEventArgs e)
{
var listViewItem = (ListViewItem)ListView.ItemContainerGenerator.ContainerFromItem(ListView.Items[0]);
var contextMenu = listViewItem.ContextMenu!;
contextMenu.IsOpen = true;
contextMenu.IsOpen = false;
}
Nothing worked.
Just use data binding:
<ContextMenu ...
DataContext="{Binding PlacementTarget.DataContext,
RelativeSource={RelativeSource Self}}">
In case you would have to set this Binding on multiple ContextMenus, declare an appropriate Setter in a global Style like
<Window.Resources>
<Style TargetType="ContextMenu">
<Setter Property="DataContext"
Value="{Binding PlacementTarget.DataContext,
RelativeSource={RelativeSource Self}}"/>
</Style>
</Window.Resources>