The WPF DataGrid is the goto grid-like control for desktop development.
Create the columns first
<DataGrid x:Name="processListDataGrid"
EnableRowVirtualization="True"
AutoGenerateColumns="False"
RowDetailsVisibilityMode="VisibleWhenSelected"
>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Padding="5">Testing One Two Three</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The code above says: "For each item in the DataGrid create a Button that has the text shown".
This is the result.
These buttons have implicit binding to the Text shown inside the button tag. Similar to innerText in HTML this property is call 'content' instead.
In all of our DataGrid columns we prefer the DataGridTemplateColumn approach. We do this to take advantage of premade user controls like the Button. The current theme is applied as a result.
Binding to a Collection of Items
The data we show, in our case, is that of an ObservableCollection which is recommended in WPF land.
The binding can be done in the code behind as follows:
var content = new ObservableCollection<ProcessInfo>(new ProcessList());
processListDataGrid.ItemsSource = content;
By setting the items source directly we are providing the data the grid needs. However, nothing showed until we told each column the property name to show.
<DataGrid.Columns>
<DataGridTemplateColumn Width="120">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Tag="{Binding id}" Click="OnKillProcess" HorizontalAlignment="Stretch">Stop Process</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Style="{DynamicResource gray}" Text="{Binding id}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Style="{DynamicResource gray}" Text="{Binding windowtitle}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Style="{DynamicResource gray}" Text="{Binding processname}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
For first timers, none of this is intuitive, and even for those returning to WPF there's plenty of stumbling blocks. Keep in mind that our data model defines the fields.
Data Model
public class ProcessInfo {
public string id { get; set; }
public string windowtitle { get; set; }
public string processname { get; set; }
}
Don't forget the getters and setters!
Where did the data come from?
public class ProcessList : List<ProcessInfo> {
public ProcessList() {
var items = Process.GetProcesses().Where(item => item.MainWindowTitle != string.Empty ||
item.ProcessName.Contains("chromedriver")).ToList();
items.ForEach(item =>
{
this.Add(new ProcessInfo()
{
id = item.Id.ToString(),
windowtitle = item.MainWindowTitle,
processname = item.ProcessName,
});
});
}
}
Of course, the System.Diagnostics.Process class!
The Button Click Handler
private async void OnKillProcess(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
int id = int.Parse(btn.Tag.ToString());
await Task.Run(() =>
{
var proc = Process.GetProcessById(id);
proc.Kill();
});
this.RefreshView();
}
Async first to stop any GUI lag.
It's all simple once we know how, it's easily remembered if we log how to do it on Dev.to.
JWP2021 WPF UserControl Process
Top comments (2)
Ah yes Sebastian; I forgot about the ICommand and it's RelayCommand brother. Thanks for reminder!
If we make our eventHandlers async, and put in code like this:
The GUI Will never freeze and the next statement after the await doesn't execute until the Task is done! Kind of like a built in state machine for the button and the view.
Also, the button tag contains the process ID.