What is your computer doing with all that memory? Write your own memory browser
What is your computer doing with all that memory? There are various kinds of memory allocated and used in each process. These include:
· Managed memory (VB.Net, C#, managed C++)
· Heap memory
· Stacks
· Images (files loaded into a process)
VirtualAlloc is the basis of these allocations. If a process needs more of these types of memory, VirtualAlloc is called.
You can inspect the various kinds of memory by running the sample code below. It offers a list of current processes, from which you dbl-click one to inspect.
Once you choose a process, a snapshot of all Virtual memory allocations is shown in a ListView, along with the size, type, etc. If it’s an image, the filename is shown too. Hovering over addresses shows the memory contents in a tooltip. Double click to open another window with the entire memory contents. You can read strings in memory in these displays.
One can imagine this being the base of your own custom Task Manager. Perhaps you want to search for all strings in all processes. “Microsoft” occurs quite often!
Imagine taking a memory snapshot of a process, doing something in that process, then taking another snapshot and displaying the difference.
Caveats: you may not have rights to read some processes or some memory. Also, if you build to 32 bit (Project Options->Compile->AdvancedCompileOptions->TargetCPU->AnyCpu) and run on 64 bit, you might have problems.
It can examine both 32 and 64 bit processes. If you get Access Denied on some processes, you might have better luck running it as Administrator
Start Visual Studio 2008
File->New Project->VB WPF Application.
Open Window1.xaml.vb, replace the contents (about 650 lines) with the sample below.
See also:
Create your own media browser: Display your pictures, music, movies in a XAML tooltip
How to Create dynamic XAML to display arbitrary XML
Remove double spaces from pasted code samples in blog
<code sample>
Imports System.Runtime.InteropServices
Class Window1
Private WithEvents _btnRefresh As Button
Private Sub Window1_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Me.Width = 800
Me.Height = 800
RefreshContent()
End Sub
Sub RefreshContent()
Dim q = From proc In System.Diagnostics.Process.GetProcesses _
Select proc.Id, _
proc.ProcessName, _
proc.WorkingSet64, _
proc.PrivateMemorySize64, _
Is64 = ProcessType(proc) _
Order By ProcessName
Me.Title = "Choose a process. Count = " + q.Count.ToString
Dim XAML = _
<Grid
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Button Name="btnRefresh">Refresh</Button>
</StackPanel>
<DockPanel Grid.Row="1" Name="dp">
</DockPanel>
</Grid>
Dim gr = CType(System.Windows.Markup.XamlReader.Load(XAML.CreateReader), Grid)
_btnRefresh = CType(gr.FindName("btnRefresh"), Button)
Dim dp = CType(gr.FindName("dp"), DockPanel)
dp.Children.Add(New Browse(q, delDblClick:=AddressOf OnProcWindDblClick))
Me.Content = gr
End Sub
Shared Function ProcessType(ByVal proc As Process) As String
Dim is32 = False
If IntPtr.Size = 4 Then ' 64 bit=8, 32=4
is32 = True
End If
Dim retProcType = "64"
Try
Dim IsrunningUnderWow64 = False
If IsWow64Process(proc.Handle, IsrunningUnderWow64) AndAlso IsrunningUnderWow64 Then
End If
If IsrunningUnderWow64 OrElse is32 Then
retProcType = "32"
End If
Catch ex As Exception
retProcType = ex.Message
End Try
Return retProcType
End Function
<DllImport("Kernel32.dll", SetLastError:=True, CallingConvention:=CallingConvention.Winapi)> _
Public Shared Function IsWow64Process( _
ByVal hProcess As IntPtr, _
<MarshalAs(UnmanagedType.Bool)> ByRef wow64Process As Boolean) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
Sub OnbtnRefresh_Click() Handles _btnRefresh.Click
RefreshContent()
End Sub
Sub OnProcWindDblClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
Try
Dim lv = TryCast(sender, Browse)
If lv IsNot Nothing Then
Dim tb = TryCast(e.OriginalSource, TextBlock)
If tb IsNot Nothing Then
Dim dt = tb.DataContext
If dt IsNot Nothing Then
Dim procTarget = tb.Text
Dim q = From a In Process.GetProcessesByName(procTarget)
If q.Count > 0 Then
Dim oWin = New VirtMemWindow(q.First) With {.Owner = Me}
End If
End If
End If
End If
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Class VirtMemWindow
Inherits Window
Private _proc As Process
Private WithEvents _btnAggregate As Button
Private _Is32 As Boolean
Sub New(ByVal proc As Process)
Dim mbi As New MEMORY_BASIC_INFORMATION
_proc = proc
Dim ptype = ProcessType(_proc)
If ptype = "32" Then
_Is32 = True
ElseIf ptype = "64" Then
_Is32 = False
Else
Throw New InvalidOperationException(ptype)
End If
If IntPtr.Size = 4 AndAlso Not _Is32 Then
Throw New InvalidOperationException("32 bit app can't examine 64 bit process")
End If
Dim lpMem As UInt64 = 0
Do While VirtualQueryEx(_proc.Handle, New UIntPtr(lpMem), mbi, Marshal.SizeOf(mbi)) = Marshal.SizeOf(mbi)
_virtAllocs.Add(mbi)
lpMem = mbi.BaseAddress.ToUInt64 + mbi.RegionSize.ToUInt64
Loop
Dim XAML = _
<Grid
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid.RowDefinitions>
<RowDefinition MaxHeight="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Button Name="btnAgg">Aggregate</Button>
</StackPanel>
<DockPanel Grid.Row="1" Name="dp">
</DockPanel>
</Grid>
Dim gr = CType(System.Windows.Markup.XamlReader.Load(XAML.CreateReader), Grid)
_btnAggregate = CType(gr.FindName("btnAgg"), Button)
Dim dp = CType(gr.FindName("dp"), DockPanel)
If _virtAllocs.Count = 0 Then
dp.Children.Add(New TextBlock With {.Text = "No items found. Perhaps 64/32 bit or Rights issue"})
Else
Dim q = From a In _virtAllocs _
Select AllocationBase = Format64or32(a.AllocationBase), _
BaseAddress = Format64or32(a.BaseAddress), _
RegionSize = Format64or32(a.RegionSize), _
Filename = GetFileNameFromMBI(a), _
AllocationProtect = a.AllocationProtect.ToString, _
AllocationState = a.State.ToString, _
Type = CType(a.lType, AllocationType).ToString _
Order By Filename.ToString.Length Descending
Me.Title += String.Format("{0} Virtual Memory Use #items={1} ", proc.ProcessName, q.Count)
dp.Children.Add(New Browse(q, _
delMouseMove:=AddressOf On_VirtmemMouseMove, _
deldblclick:=AddressOf On_VirtmemDblClick))
End If
Me.Content = gr
Me.Show()
End Sub
Function Format64or32(ByVal num As UIntPtr) As String
Dim retval = ""
If _Is32 Then
retval = num.ToUInt32.ToString("x8")
Else
retval = num.ToUInt64.ToString("x16")
End If
Return retval
End Function
Sub OnbtnAggClick() Handles _btnAggregate.Click
Dim qAgg = From a In _virtAllocs _
Group By AllocationType = a.lType.ToString Into Tot = Sum(a.RegionSize.ToUInt64), Cnt = Count(a.RegionSize.ToUInt64 > 0)
Dim oWin = New Window With {.Title = "Aggregate"}
oWin.Content = New Browse(qAgg)
oWin.Owner = Me
oWin.Show()
End Sub
Private _virtAllocs As New List(Of MEMORY_BASIC_INFORMATION)
Sub On_VirtmemDblClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
Try
Dim lv = TryCast(sender, Browse)
If lv IsNot Nothing Then
Dim tb = TryCast(e.OriginalSource, TextBlock)
If tb IsNot Nothing Then
Select Case tb.Name
Case "AllocationBase", "BaseAddress"
ClearPriorToolTipIfAny()
Dim baseAddr = Convert.ToInt64(tb.Text, 16) ' convert from hex (base 16)
Dim allocQuery = From a In _virtAllocs _
Where If(tb.Name = "AllocationBase", a.AllocationBase, a.BaseAddress) = baseAddr
If allocQuery.Count > 0 Then
Dim virtAlloc = allocQuery.First
Dim strAddrDump = GetAddressDump( _
virtAlloc.BaseAddress, _
virtAlloc.RegionSize, _
nMax:=0 _
)
Dim tbxDump = New TextBox With { _
.Text = strAddrDump, _
.VerticalScrollBarVisibility = ScrollBarVisibility.Auto, _
.BorderThickness = New Windows.Thickness(0), _
.FontFamily = New FontFamily("Courier New"), _
.FontSize = 10 _
}
Dim oWin = New Window With { _
.Title = String.Format("Mem dump Address = {0:x8}, size = {1}", _
tb.Text, _
virtAlloc.RegionSize _
) _
}
oWin.Content = tbxDump
oWin.Owner = Me
oWin.Show()
End If
End Select
End If
End If
Catch ex As Exception
End Try
End Sub
Sub ClearPriorToolTipIfAny()
If _LastTipObj IsNot Nothing Then
Dim lastTip = CType(_LastTipObj.ToolTip, ToolTip)
lastTip.IsOpen = False
_LastTipObj = Nothing
End If
End Sub
Private _LastTipObj As FrameworkElement
Sub On_VirtmemMouseMove(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim lv = TryCast(sender, Browse)
If lv IsNot Nothing Then
Dim tb = TryCast(e.OriginalSource, TextBlock)
If tb IsNot Nothing Then
' Dim o = lv.ItemContainerGenerator.ContainerFromItem(tb)
If _LastTipObj IsNot Nothing Then
Dim lastTip = CType(_LastTipObj.ToolTip, ToolTip)
If tb Is _LastTipObj Then ' over same obj: don't create a new tip
Return
Else
' different object: close the tip
lastTip.IsOpen = False
End If
End If
Dim dt = tb.DataContext
If dt IsNot Nothing Then
Dim ttipObj = New ToolTip
ttipObj.PlacementTarget = tb
ttipObj.Placement = Controls.Primitives.PlacementMode.Bottom
Select Case tb.Name
Case "AllocationBase", "BaseAddress"
Dim baseAddr = Convert.ToUInt64(tb.Text, 16) ' convert from hex (base 16)
Dim q = From a In _virtAllocs _
Where If(tb.Name = "AllocationBase", a.AllocationBase, a.BaseAddress) = baseAddr
If q.Count > 0 Then
Dim mbi = q.First
Dim baddr = If(tb.Name = "AllocationBase", mbi.AllocationBase, mbi.BaseAddress)
If CType(mbi.State, AllocationState) <> AllocationState.MEM_FREE AndAlso baddr.ToUInt64 > &H1000 Then
Dim strAddrDump = GetAddressDump( _
baddr, _
mbi.RegionSize)
Dim tbxDump = New TextBox With { _
.Text = strAddrDump, _
.BorderThickness = New Windows.Thickness(0), _
.FontFamily = New FontFamily("Courier New"), _
.FontSize = 10, _
.Background = Brushes.LightYellow _
}
Dim sp = New StackPanel With {.Orientation = Orientation.Vertical}
If CType(mbi.lType, AllocationType) = AllocationType.MEM_IMAGE Then
Dim sbFilename As New Text.StringBuilder(300)
Dim rr = GetFileNameFromMBI(mbi)
GetModuleFileNameEx(_proc.Handle, New UIntPtr(mbi.AllocationBase.ToUInt64), sbFilename, sbFilename.Capacity)
sp.Children.Add(New TextBlock With {.Text = "Filename = " + sbFilename.ToString})
End If
sp.Children.Add(tbxDump)
ttipObj.Content = sp
Else
ttipObj.Content = baddr.ToString + " bad address for mem dump"
End If
End If
Case "RegionSize"
ttipObj.Content = String.Format(" Base 10: {0}", Convert.ToInt32(tb.Text, 16))
Case "Where", "Filename"
ttipObj.Content = tb.Text
Case Else
Return
End Select
ToolTipService.SetShowDuration(tb, 100000)
ttipObj.IsOpen = True
tb.ToolTip = ttipObj
_LastTipObj = tb
End If
End If
End If
End Sub
Const BlockSize As Integer = 1024
<StructLayout(LayoutKind.Sequential)> _
Structure ProcMemBlock
<MarshalAs(UnmanagedType.ByValArray, sizeconst:=BlockSize)> _
Dim data() As Byte
End Structure
Public Function GetAddressDump(ByVal nAddress As UIntPtr, ByVal nTotalBytesToRead As UIntPtr, Optional ByVal nMax As Integer = 1024) As String
Dim sbAddrDump As New Text.StringBuilder
Try
If nMax > 0 Then
nTotalBytesToRead = Math.Min(nMax, nTotalBytesToRead.ToUInt64)
End If
Dim blk = New ProcMemBlock
Dim nBytesLeftToRead = nTotalBytesToRead
Do While nBytesLeftToRead.ToUInt64 > 0
Dim nBytesToReadForThisBlock = Math.Min(nBytesLeftToRead.ToUInt64, BlockSize)
Dim nBytesRead As UIntPtr = 0
If ReadProcessMemory(_proc.Handle, nAddress, blk, nBytesToReadForThisBlock, nBytesRead) Then
nBytesLeftToRead = nBytesLeftToRead.ToUInt64 - nBytesRead.ToUInt64
Dim innerCnt = 0
Dim strBytes = ""
Dim strChars = ""
For i = 0 To nBytesRead.ToUInt64 - 1 Step 4
If innerCnt Mod 8 = 0 Then
If i > 0 Then ' if we're ending a line, dump out the bytes and chars
sbAddrDump.AppendLine(" " + strBytes + " " + strChars)
strBytes = ""
strChars = ""
Else
sbAddrDump.AppendLine()
End If
If _Is32 Then
sbAddrDump.Append(String.Format("{0:x8} : ", nAddress.ToUInt32 + CUInt(i)))
Else
Dim offset = nAddress.ToUInt64 + CUInt(i)
sbAddrDump.Append(String.Format("{0:x16} : ", nAddress.ToUInt64 + CUInt(i)))
End If
End If
innerCnt += 1
Dim dword = 0
For p = 0 To 3
dword += CInt(blk.data(i + p)) << shifts(3 - p)
Next
' Dim dword = Marshal.ReadIntPtr(New IntPtr(nAddress + i)).ToInt32
toBytes(dword, strBytes, strChars)
sbAddrDump.Append(String.Format("{0:x8} ", dword))
Next
' some leftovers
Dim startLeftover = innerCnt
Do While innerCnt Mod 8 > 0
sbAddrDump.Append(" ")
innerCnt += 1
Loop
sbAddrDump.AppendLine(" " + strBytes + " ")
innerCnt = startLeftover
Do While innerCnt Mod 8 > 0
sbAddrDump.Append(" ")
innerCnt += 1
Loop
sbAddrDump.AppendLine(strChars)
Do While innerCnt Mod 8 > 0
sbAddrDump.Append(" ")
innerCnt += 1
Loop
Else
Exit Do
End If
nAddress = nAddress.ToUInt64 + nBytesRead.ToUInt64
Loop
Catch ex As Exception
sbAddrDump.Append("Exception when reading " + nAddress.ToString + " " + ex.Message)
End Try
Return sbAddrDump.ToString
End Function
Const badchars = vbCr + vbLf + vbTab + Chr(&HB) + Chr(&HC)
Private Shared shifts As Integer() = {24, 16, 8, 0}
Shared Sub toBytes(ByVal num As Integer, ByRef strBytes As String, ByRef strChars As String)
For i = 3 To 0 Step -1
Dim abyte = CByte((num >> shifts(i) And &HFF))
strBytes += String.Format("{0:x2} ", abyte)
Dim thechar = " "
If abyte > 15 AndAlso abyte < 127 Then
If badchars.IndexOf(thechar) < 0 Then
thechar = Chr(abyte).ToString
End If
End If
strChars += thechar
Next
End Sub
<DllImport("kernel32.dll", SetLastError:=True)> _
Public Shared Function ReadProcessMemory( _
ByVal hProcess As IntPtr, _
ByVal lpBaseAddress As UIntPtr, _
ByRef lpBuffer As ProcMemBlock, _
ByVal dwSize As Integer, _
ByRef lpNumberOfBytesRead As Integer _
) As Integer
End Function
<DllImport("psapi")> _
Shared Function GetModuleFileNameEx(ByVal hProcess As IntPtr, ByVal hModule As UIntPtr, ByVal lpFileName As Text.StringBuilder, ByVal nSize As Integer) As Integer
End Function
<DllImport("kernel32")> _
Shared Function VirtualQueryEx( _
ByVal hProcess As IntPtr, _
ByVal lpAddress As UIntPtr, _
ByRef mbi As MEMORY_BASIC_INFORMATION, _
ByVal dwLength As UInteger) As UInteger
End Function
<StructLayout(LayoutKind.Sequential)> _
Structure MEMORY_BASIC_INFORMATION
Dim BaseAddress As UIntPtr
Dim AllocationBase As UIntPtr
Dim AllocationProtect As AllocationProtect
Dim RegionSize As UIntPtr
Dim State As AllocationState
Dim Protect As AllocationProtect
Dim lType As AllocationType
End Structure
<Flags()> _
Enum AllocationProtect
PAGE_EXECUTE = &H10
PAGE_EXECUTE_READ = &H20
PAGE_EXECUTE_READWRITE = &H40
PAGE_EXECUTE_WRITECOPY = &H80
PAGE_NOACCESS = &H1
PAGE_READONLY = &H2
PAGE_READWRITE = &H4
PAGE_WRITECOPY = &H8
PAGE_GUARD = &H100
PAGE_NOCACHE = &H200
PAGE_WRITECOMBINE = &H400
End Enum
<Flags()> _
Enum AllocationType
MEM_IMAGE = &H1000000
MEM_MAPPED = &H40000
MEM_PRIVATE = &H20000
End Enum
<Flags()> _
Enum AllocationState
MEM_COMMIT = &H1000
MEM_FREE = &H10000
MEM_RESERVE = &H2000
End Enum
Function GetFileNameFromMBI(ByVal mbi As MEMORY_BASIC_INFORMATION) As String
Dim retval = ""
If CType(mbi.lType, AllocationType) = AllocationType.MEM_IMAGE Or True Then
If mbi.AllocationBase.ToUInt64 > 0 Then
Dim sbFilename As New Text.StringBuilder(300)
If GetModuleFileNameEx(_proc.Handle, New UIntPtr(mbi.AllocationBase.ToUInt64), sbFilename, sbFilename.Capacity) > 0 Then
retval = sbFilename.ToString
End If
End If
End If
Return retval
End Function
End Class
End Class
' see https://blogs.msdn.com/calvin\_hsia/archive/2007/12/06/6684376.aspx
Public Class Browse
Inherits ListView
Private _Browse As Browse = Me
Private _delDblClick As BrowEventDelegate
Private _delMouseMove As BrowEventDelegate
Public _query As IEnumerable
Delegate Sub BrowEventDelegate(ByVal sender As Object, ByVal e As RoutedEventArgs)
Sub New( _
ByVal Query As IEnumerable, _
Optional ByVal delDblClick As BrowEventDelegate = Nothing, _
Optional ByVal delMouseMove As BrowEventDelegate = Nothing, _
Optional ByVal fAllowHeaderClickSort As Boolean = True)
_query = Query
Dim gv As New GridView
_Browse.View = gv
_Browse.ItemsSource = Query
If fAllowHeaderClickSort Then
_Browse.AddHandler(GridViewColumnHeader.ClickEvent, New RoutedEventHandler(AddressOf HandleHeaderClick))
End If
If delDblClick IsNot Nothing Then
_delDblClick = delDblClick
_Browse.AddHandler(MouseDoubleClickEvent, New RoutedEventHandler(AddressOf OnRowDblClickEvent))
End If
If delMouseMove IsNot Nothing Then
_delMouseMove = delMouseMove
_Browse.AddHandler(MouseMoveEvent, New RoutedEventHandler(AddressOf OnMouseMoveEvent))
End If
If Query.GetType.GetInterface(GetType(IEnumerable(Of )).FullName).GetGenericArguments(0).Name = "XElement" Then ' It's XML
Dim Elem1 = CType(Query, IEnumerable(Of XElement))(0).Elements ' Thanks Avner!
For Each Item In Elem1
Dim gvc As New GridViewColumn
gvc.Header = Item.Name.LocalName
gv.Columns.Add(gvc)
Dim bind As New Binding("Element[" + Item.Name.LocalName + "].Value")
gvc.DisplayMemberBinding = bind
gvc.Width = 180
Next
Else ' it's some anonymous type like "VB$AnonymousType_1`3". Let's use reflection to get the column names
For Each mem In From mbr In _
Query.GetType().GetInterface(GetType(IEnumerable(Of )).FullName) _
.GetGenericArguments()(0).GetMembers _
Where mbr.MemberType = Reflection.MemberTypes.Property
Dim datatype = CType(mem, Reflection.PropertyInfo)
Dim coltype = datatype.PropertyType.Name
Select Case coltype
Case "Int32", "String", "Int64"
Dim gvc As New GridViewColumn
gvc.Header = mem.Name
gv.Columns.Add(gvc)
Dim XAMLdt = _
<DataTemplate
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<StackPanel Orientation="Horizontal">
<TextBlock Name=<%= mem.Name %>
Text=<%= If(coltype = "String", _
"{Binding Path=" + mem.Name + "}", _
"{Binding Path=" + mem.Name + "}") %>
>
</TextBlock>
</StackPanel>
</DataTemplate>
gvc.CellTemplate = CType(System.Windows.Markup.XamlReader.Load(XAMLdt.CreateReader), DataTemplate)
If coltype <> "String" Then
' gvc.Width = 80
gvc.Width = Double.NaN ' auto
Else
' gvc.DisplayMemberBinding = New Binding(mem.Name)
' gvc.Width = 180
gvc.Width = Double.NaN ' auto
End If
End Select
Next
End If
Dim XAMLlbStyle = _
<Style
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
TargetType="ListBoxItem">
<Setter Property="Foreground" Value="Blue"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="Aquamarine"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
_Browse.ItemContainerStyle = CType(Windows.Markup.XamlReader.Load(XAMLlbStyle.CreateReader), Windows.Style)
End Sub
Sub OnRowDblClickEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
If _delDblClick IsNot Nothing Then
_delDblClick.Invoke(sender, e)
End If
End Sub
Sub OnMouseMoveEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
If _delMouseMove IsNot Nothing Then
_delMouseMove.Invoke(sender, e)
End If
End Sub
Dim _Lastdir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending
Dim _LastHeaderClicked As GridViewColumnHeader = Nothing
Sub HandleHeaderClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
If e.OriginalSource.GetType Is GetType(GridViewColumnHeader) Then
Dim gvh = CType(e.OriginalSource, GridViewColumnHeader)
Dim dir As System.ComponentModel.ListSortDirection = ComponentModel.ListSortDirection.Ascending
If Not gvh Is Nothing AndAlso Not gvh.Column Is Nothing Then
If _LastHeaderClicked IsNot Nothing Then
_LastHeaderClicked.Column.HeaderTemplate = Nothing
End If
Dim hdr = gvh.Column.Header
If gvh Is _LastHeaderClicked Then
If _Lastdir = ComponentModel.ListSortDirection.Ascending Then
dir = ComponentModel.ListSortDirection.Descending
End If
End If
gvh.Column.HeaderTemplate = CType(Windows.Markup.XamlReader.Load( _
<DataTemplate
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
>
<DockPanel>
<TextBlock HorizontalAlignment="Center"
Text="{Binding}"/>
<Path
Fill="DarkGray"
Data=<%= If(dir = ComponentModel.ListSortDirection.Ascending, _
"M 5,10 L 15,10 L 10,5", _
"M 5,5 L 10,10 L 15,5") %>/>
</DockPanel>
</DataTemplate>.CreateReader), DataTemplate)
Sort(CStr(hdr), dir)
_LastHeaderClicked = gvh
_Lastdir = dir
End If
End If
End Sub
Sub Sort(ByVal sortby As String, ByVal dir As System.ComponentModel.ListSortDirection)
_Browse.Items.SortDescriptions.Clear()
Dim sd = New System.ComponentModel.SortDescription(sortby, dir)
_Browse.Items.SortDescriptions.Add(sd)
_Browse.Items.Refresh()
End Sub
End Class
</code sample>
Comments
- Anonymous
October 26, 2009
I'd wrap the all the win32 calls in their own class. Make them all static methods and then they can be called from anywhere without warming up their object.