Skip to content

Commit 6c2dd3f

Browse files
committed
* Added sorting to process analyzer
* Added ability to kill a process to the process analyzer
1 parent eb88f03 commit 6c2dd3f

File tree

8 files changed

+207
-11
lines changed

8 files changed

+207
-11
lines changed

MemPlus/Business/PROCESS/ProcessDetail.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
3-
namespace MemPlus.Business.PROCESS
1+
namespace MemPlus.Business.PROCESS
42
{
53
/// <summary>
64
/// Internal class that represents the presentable details of a Process object
@@ -23,5 +21,9 @@ internal class ProcessDetail
2321
/// The current memory usage of the Process in MB
2422
/// </summary>
2523
public string MemoryUsage { get; set; }
24+
/// <summary>
25+
/// The current memory usage of the Process
26+
/// </summary>
27+
public long MemoryUsageLong { get; set; }
2628
}
2729
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Windows;
4+
using System.Windows.Controls;
5+
using System.Windows.Controls.Primitives;
6+
using System.Windows.Media;
7+
8+
namespace MemPlus.Business.UTILS
9+
{
10+
/// <summary>
11+
/// Internal logic for sorting a GridView
12+
/// </summary>
13+
internal class GridViewSort
14+
{
15+
#region Properties
16+
17+
/// <summary>
18+
/// Check whether sorting is enabled or not
19+
/// </summary>
20+
/// <param name="obj">The DependencyObject that needs to be checked for sorting</param>
21+
/// <returns>True if sorting is enabled, otherwise false</returns>
22+
internal static bool GetEnabled(DependencyObject obj)
23+
{
24+
return (bool)obj.GetValue(EnabledProperty);
25+
}
26+
27+
/// <summary>
28+
/// Enable or disable sorting
29+
/// </summary>
30+
/// <param name="obj">The DependencyObject that needs to have the Enabled property for sorting changed</param>
31+
/// <param name="value">True if enabled, otherwise false</param>
32+
internal static void SetEnabled(DependencyObject obj, bool value)
33+
{
34+
obj.SetValue(EnabledProperty, value);
35+
}
36+
37+
/// <summary>
38+
/// The EnabledProperty that can be used by DependencyObjects
39+
/// </summary>
40+
internal static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(GridViewSort),
41+
new UIPropertyMetadata(
42+
false,
43+
(o, e) =>
44+
{
45+
// ReSharper disable once UsePatternMatching
46+
ListView listView = o as ListView;
47+
if (listView == null) return;
48+
bool oldValue = (bool)e.OldValue;
49+
bool newValue = (bool)e.NewValue;
50+
if (oldValue && !newValue)
51+
{
52+
listView.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
53+
}
54+
if (!oldValue && newValue)
55+
{
56+
listView.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ColumnHeader_Click));
57+
}
58+
}
59+
)
60+
);
61+
62+
/// <summary>
63+
/// Get the name of a property
64+
/// </summary>
65+
/// <param name="obj">The DependencyObject</param>
66+
/// <returns>The name of a property</returns>
67+
internal static string GetPropertyName(DependencyObject obj)
68+
{
69+
return (string)obj.GetValue(PropertyNameProperty);
70+
}
71+
72+
/// <summary>
73+
/// Set the name of a property
74+
/// </summary>
75+
/// <param name="obj">The DependencyObject</param>
76+
/// <param name="value">The name of the property</param>
77+
internal static void SetPropertyName(DependencyObject obj, string value)
78+
{
79+
obj.SetValue(PropertyNameProperty, value);
80+
}
81+
82+
/// <summary>
83+
/// Using a DependencyProperty as the backing store for PropertyName. This enables animation, styling, binding, etc...
84+
/// </summary>
85+
internal static readonly DependencyProperty PropertyNameProperty =
86+
DependencyProperty.RegisterAttached(
87+
"PropertyName",
88+
typeof(string),
89+
typeof(GridViewSort),
90+
new UIPropertyMetadata(null)
91+
);
92+
93+
#endregion
94+
95+
#region ColumnHeader
96+
97+
/// <summary>
98+
/// Method that is called when a ColumnHeader object is clicked
99+
/// </summary>
100+
/// <param name="sender">The object that called this method</param>
101+
/// <param name="e">The RoutedEventArgs</param>
102+
private static void ColumnHeader_Click(object sender, RoutedEventArgs e)
103+
{
104+
if (!(e.OriginalSource is GridViewColumnHeader headerClicked)) return;
105+
string propertyName = GetPropertyName(headerClicked.Column);
106+
if (string.IsNullOrEmpty(propertyName)) return;
107+
ListView listView = GetAncestor<ListView>(headerClicked);
108+
if (listView == null) return;
109+
if (GetEnabled(listView))
110+
{
111+
ApplySort(listView.Items, propertyName);
112+
}
113+
}
114+
115+
#endregion
116+
117+
#region Helpermethods
118+
119+
/// <summary>
120+
/// Get the parent object of a DependencyObject
121+
/// </summary>
122+
/// <typeparam name="T">DependencyObject</typeparam>
123+
/// <param name="reference">A DependencyObject</param>
124+
/// <returns>The parent of a DependencyObject</returns>
125+
internal static T GetAncestor<T>(DependencyObject reference) where T : DependencyObject
126+
{
127+
DependencyObject parent = VisualTreeHelper.GetParent(reference);
128+
while (!(parent is T))
129+
{
130+
parent = VisualTreeHelper.GetParent(parent ?? throw new InvalidOperationException());
131+
}
132+
return (T)parent;
133+
}
134+
135+
/// <summary>
136+
/// Apply the sorting to the content of a ICollectionView
137+
/// </summary>
138+
/// <param name="view">The ICollectionView</param>
139+
/// <param name="propertyName">The name of the property</param>
140+
internal static void ApplySort(ICollectionView view, string propertyName)
141+
{
142+
ListSortDirection direction = ListSortDirection.Ascending;
143+
if (view.SortDescriptions.Count > 0)
144+
{
145+
SortDescription currentSort = view.SortDescriptions[0];
146+
if (currentSort.PropertyName == propertyName)
147+
{
148+
direction = currentSort.Direction == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
149+
}
150+
view.SortDescriptions.Clear();
151+
}
152+
if (!string.IsNullOrEmpty(propertyName))
153+
{
154+
view.SortDescriptions.Add(new SortDescription(propertyName, direction));
155+
}
156+
}
157+
158+
#endregion
159+
}
160+
}

MemPlus/Business/UTILS/Utils.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ internal static List<ProcessDetail> GetProcessDetails(LogController logControlle
246246
ProcessName = p.ProcessName,
247247
ProcessLocation = p.MainModule.FileName,
248248
MemoryUsage = (p.WorkingSet64 / (1024 * 1024)).ToString("F2") + " MB",
249+
MemoryUsageLong = p.WorkingSet64
249250
};
250251
processDetailsList.Add(pd);
251252
}

MemPlus/MemPlus.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
<Compile Include="Business\RAM\RamData.cs" />
100100
<Compile Include="Business\EXPORT\RamSticksExporter.cs" />
101101
<Compile Include="Business\RAM\RamStick.cs" />
102+
<Compile Include="Business\UTILS\GridViewSort.cs" />
102103
<Compile Include="Business\UTILS\HotKeyController.cs" />
103104
<Compile Include="Business\UTILS\NativeMethods.cs" />
104105
<Compile Include="Business\UTILS\Utils.cs" />

MemPlus/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@
5151
// You can specify all the values or you can default the Build and Revision Numbers
5252
// by using the '*' as shown below:
5353
// [assembly: AssemblyVersion("1.0.*")]
54-
[assembly: AssemblyVersion("1.3.1.0")]
55-
[assembly: AssemblyFileVersion("1.3.1.0")]
54+
[assembly: AssemblyVersion("1.3.2.0")]
55+
[assembly: AssemblyFileVersion("1.3.2.0")]

MemPlus/Views/Windows/AboutWindow.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<LineBreak />
3030
Images: small-n-flat by paomedia<LineBreak />
3131
Theme: Syncfusion<LineBreak />
32-
Version: 1.3.1.0<LineBreak />
32+
Version: 1.3.2.0<LineBreak />
3333
<LineBreak />
3434
Copyright © CodeDead 2018
3535
</TextBlock>

MemPlus/Views/Windows/ProcessAnalyzerWindow.xaml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
66
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
77
xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
8+
xmlns:utils="clr-namespace:MemPlus.Business.UTILS"
89
mc:Ignorable="d" UseLayoutRounding="True"
910
WindowStartupLocation="CenterScreen" AllowsTransparency="True"
1011
TitleTextAlignment="Center" UseNativeChrome="True"
@@ -14,14 +15,22 @@
1415
<RowDefinition />
1516
<RowDefinition Height="Auto" />
1617
</Grid.RowDefinitions>
17-
<ListView x:Name="LsvProcessList" SelectionMode="Single">
18+
<ListView x:Name="LsvProcessList"
19+
SelectionMode="Single"
20+
IsSynchronizedWithCurrentItem="True"
21+
utils:GridViewSort.Enabled="True">
1822
<ListView.ContextMenu>
1923
<ContextMenu>
2024
<MenuItem Header="Empty working set" Click="EmptyWorkingSetMenuItem_OnClick">
2125
<MenuItem.Icon>
2226
<Image Width="16" Height="16" Source="/MemPlus;component/Resources/Images/process.png"></Image>
2327
</MenuItem.Icon>
2428
</MenuItem>
29+
<MenuItem Header="Kill" Click="KillMenuItem_OnClick">
30+
<MenuItem.Icon>
31+
<Image Width="16" Height="16" Source="/MemPlus;component/Resources/Images/delete.png"></Image>
32+
</MenuItem.Icon>
33+
</MenuItem>
2534
<Separator />
2635
<MenuItem Header="Copy" Click="CopyMenuItem_OnClick">
2736
<MenuItem.Icon>
@@ -48,10 +57,10 @@
4857
</ListView.ContextMenu>
4958
<ListView.View>
5059
<GridView x:Name="DynGrid">
51-
<GridViewColumn Header="Process ID" DisplayMemberBinding="{Binding ProcessId}" />
52-
<GridViewColumn Header="Process name" DisplayMemberBinding="{Binding ProcessName}" />
53-
<GridViewColumn Header="Process location" DisplayMemberBinding="{Binding ProcessLocation}" />
54-
<GridViewColumn Header="Memory usage" DisplayMemberBinding="{Binding MemoryUsage}" />
60+
<GridViewColumn Header="Process ID" DisplayMemberBinding="{Binding ProcessId}" utils:GridViewSort.PropertyName="ProcessId" />
61+
<GridViewColumn Header="Process name" DisplayMemberBinding="{Binding ProcessName}" utils:GridViewSort.PropertyName="ProcessName" />
62+
<GridViewColumn Header="Process location" DisplayMemberBinding="{Binding ProcessLocation}" utils:GridViewSort.PropertyName="ProcessLocation" />
63+
<GridViewColumn Header="Memory usage" DisplayMemberBinding="{Binding MemoryUsage}" utils:GridViewSort.PropertyName="MemoryUsageLong" />
5564
</GridView>
5665
</ListView.View>
5766
</ListView>

MemPlus/Views/Windows/ProcessAnalyzerWindow.xaml.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,5 +176,28 @@ private void EmptyWorkingSetMenuItem_OnClick(object sender, RoutedEventArgs e)
176176
MessageBox.Show(ex.Message, "MemPlus", MessageBoxButton.OK, MessageBoxImage.Error);
177177
}
178178
}
179+
180+
/// <summary>
181+
/// Method that is called when a Process should be killed
182+
/// </summary>
183+
/// <param name="sender">The object that called this method</param>
184+
/// <param name="e">The RoutedEventArgs</param>
185+
private void KillMenuItem_OnClick(object sender, RoutedEventArgs e)
186+
{
187+
if (LsvProcessList.SelectedItems.Count == 0) return;
188+
if (!(LsvProcessList.SelectedItem is ProcessDetail detail)) return;
189+
190+
try
191+
{
192+
_logController.AddLog(new ApplicationLog("Killing " + detail.ProcessName + " (" + detail.ProcessId + ")"));
193+
Process.GetProcessById(detail.ProcessId).Kill();
194+
_logController.AddLog(new ApplicationLog("Done killing " + detail.ProcessName + " (" + detail.ProcessId + ")"));
195+
}
196+
catch (Exception ex)
197+
{
198+
_logController.AddLog(new ApplicationLog(ex.Message));
199+
MessageBox.Show(ex.Message, "MemPlus", MessageBoxButton.OK, MessageBoxImage.Error);
200+
}
201+
}
179202
}
180203
}

0 commit comments

Comments
 (0)