Introduction
Here is a sample code which demonstrates how to add/edit/delete data in a WPF ListView
. Unlike thousands of articles already on the net, this one focus on usability and an intuitive behavior, rather than show causing capabilities of WPF ListView
.
What It Does
This sample code does the following:
- Loads data from an XML file MyData.xml located in application folder.
- Allows the user to add, edit and delete rows to the
listview
.
- Saves the data back to MyData.xml when Save or OK button is clicked.
What It Doesn't Do
- This is just a sample code. You will need to customize it for your own requirements.
- It doesn't handle any exceptions. I didn't test what happens if the XML file does not exist or is in a bad format.
- Doesn't follow best naming standards. I use
TextBox1
, ListView1
, Window1
, etc. The reason is you will need to change it anyway to suit your requirement when you use this code.
- The method used in the sample code is not ideal if you have large amounts of data (say if you have tens of thousands of data). You need to test it before using in the actual scenario.
Using the Code
This code is developed in C# 2010 Express edition. The following is an explanation to the code.
Creating ListViewData Class
The first step is to create a class to map the columns of the ListView
. In Visual Studio, create a new WPF Application. In the new application, add a new class named ListViewData
. This class has a Properties corresponding to each column in the ListView
. In the example, I have created two properties named Col1
and Col2
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ListViewTest
{
public class ListViewData
{
public ListViewData()
{
}
public ListViewData(string col1, string col2)
{
Col1 = col1;
Col2 = col2;
}
public string Col1 { get; set; }
public string Col2 { get; set; }
}
}
In the real scenario, you will need to create more properties with meaningful names.
Setting up the Form
Switch to Window1.xaml and change the default Grid
layout to Canvas
layout. Drag the controls from Toolbox to the form and resize to match with screenshot above. For the ListView
, add columns named col1
and col2
. Set the DisplayMemberBinding
to Col1
and Col2
respectively. Once this is finished, the resulting XAML will look like the following:
<Window x:Class="ListViewTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="373" Loaded="Window_Loaded"
Closing="Window_Closing" ResizeMode="CanMinimize">
<Canvas>
<TextBox Canvas.Left="12" Canvas.Top="12" Height="23" Name="textBox1"
Width="120" TextChanged="textBox1_TextChanged" KeyDown="textBox1_KeyDown"
GotFocus="textBox1_GotFocus" />
<TextBox Canvas.Left="146" Canvas.Top="12" Height="23" Name="textBox2"
Width="120" TextChanged="textBox2_TextChanged" GotFocus="textBox2_GotFocus"
KeyDown="textBox2_KeyDown" />
<ListView Canvas.Left="12" Canvas.Top="68" Height="183" Name="listView1"
Width="253" SelectionChanged="listView1_SelectionChanged"
SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Header="Col1" Width="150"
DisplayMemberBinding="{Binding Col1}"></GridViewColumn>
<GridViewColumn Header="Col1" Width="50"
DisplayMemberBinding="{Binding Col2}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<Button Name="addButton" Canvas.Left="122" Canvas.Top="41" Height="21"
Width="69" Click="addButton_Click" IsDefault="True">Add</Button>
<Button Name="removeButton" Canvas.Left="197" Canvas.Top="41" Height="21"
Width="69" Click="removeButton_Click">Remove</Button>
<Button Name="okButton" Canvas.Left="274" Canvas.Top="13" Content="OK"
Height="22" Width="74" Click="okButton_Click" />
<Button Name="closeButton" Canvas.Left="274" Canvas.Top="44" Content="Close"
Height="22" Width="74" Click="closeButton_Click" />
<Button Name="saveButton" Canvas.Left="274" Canvas.Top="75" Content="Save"
Height="22" Width="74" Click="saveButton_Click" />
</Canvas>
</Window>
As you have probably noticed, the key is setting the DisplayMemberBinding
. Without setting this, the ListView
will not work.
I want to work this code in this way. Whenever users type something in the textboxes, that should be immediately reflected in the SelectedItem
of the ListView
. I don't want the user to type something and then click an Edit button to do this. Simply, it is not the user friendly way. (Didn't you already notice that we miss an Edit button?).
So, I have added TextChanged
event handler for textboxes.
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
RefreshListView(textBox1.Text, textBox2.Text);
}
private void textBox2_TextChanged(object sender, TextChangedEventArgs e)
{
RefreshListView(textBox1.Text, textBox2.Text);
}
private void RefreshListView(string value1, string value2)
{
ListViewData lvc = (ListViewData)listView1.SelectedItem;
if (lvc != null && !stopRefreshControls)
{
setDataChanged(true);
lvc.Col1 = value1;
lvc.Col2 = value2;
listView1.Items.Refresh();
}
}
private void setDataChanged(bool value)
{
dataChanged = value;
saveButton.IsEnabled = value;
}
I know this can be done by binding the textboxes to ListView
's SelectedItem
. I did this way because I wanted more control. (For example, to enable or disable the Save button according to the change.)
Next, I want the col1
and col2
from ListView
to be displayed in the textboxes, when an item is selected in the ListView
. See the code.
private void listView1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListViewData lvc = (ListViewData)listView1.SelectedItem;
if (lvc != null)
{
stopRefreshControls = true;
textBox1.Text = lvc.Col1;
textBox2.Text = lvc.Col2;
stopRefreshControls = false;
}
}
stopRefreshControls
is a private
variable, which is used to avoid unnecessary updates to ListView
.
So far, so nice. When the textbox is changed, it is reflected in the ListView
. When an item in the ListView
is selected, it is shown in the textbox
. What next? When the user clicks on Add button, I want a new row to be added to the ListView
. Please note that it doesn't work the other way. Type in something and then click Add. When users want to add a row, they will search for an Add button. When they find it, they click on it and type the values. So it follows users' mental model.
Here is the code for the Add button.
private void addButton_Click(object sender, RoutedEventArgs e)
{
setDataChanged(true);
AddRow();
}
private void AddRow()
{
listView1.Items.Add(new ListViewData("", ""));
listView1.SelectedIndex = listView1.Items.Count - 1;
textBox1.Text = "";
textBox2.Text = "";
textBox1.Focus();
}
The setDataChanged
is already listed in the previous code listing.
Next let's examine the code for Remove button.
private void removeButton_Click(object sender, RoutedEventArgs e)
{
setDataChanged(true);
int selectedIndex = listView1.SelectedIndex;
listView1.Items.Remove(listView1.SelectedItem);
if (listView1.Items.Count == 0)
{
AddRow();
}
else if (selectedIndex <= listView1.Items.Count - 1)
{
listView1.SelectedIndex = selectedIndex;
}
else {
listView1.SelectedIndex = listView1.Items.Count - 1;
}
}
It is ideal to have one item selected always in the ListView
. The lines after ListView1.Items.Remove
do exactly this. It selects the item next to the one deleted. And if there are no more items to select, it simply adds one and selects it. Nice, no?
We have completed code to add, edit and delete rows in the ListView
. What's pending? Yes, we need to save items from ListView
to an XML file. In this sample, it saves to an XML File MyData.xml. However, you can customize this and save to your own XML file, database or any other data source. Saving is done in the class MyData
. MyData
has a public
method Save
.
public void Save(System.Windows.Data.CollectionView items)
{
XDocument xdoc = new XDocument();
XElement xeRoot = new XElement("Data");
XElement xeSubRoot = new XElement("Rows");
foreach (var item in items)
{
ListViewData lvc = (ListViewData)item;
XElement xRow = new XElement("Row");
xRow.Add(new XElement("col1", lvc.Col1));
xRow.Add(new XElement("col2", lvc.Col2));
xeSubRoot.Add(xRow);
}
xeRoot.Add(xeSubRoot);
xdoc.Add(xeRoot);
xdoc.Save("MyData.xml");
}
Once it is saved, we need to load and show it in the ListView
next time we start our application. For this, go back to Window1
and add Window_Loaded
event handler.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ShowData();
if (listView1.Items.Count == 0)
{
AddRow();
}
else
{
listView1.SelectedIndex = 0;
}
setDataChanged(false);
textBox1.Focus();
}
private void ShowData()
{
MyData md = new MyData();
listView1.Items.Clear();
foreach (var row in md.GetRows())
{
listView1.Items.Add(row);
}
}
AddRow
and setDataChanged
are private
functions in Window1
and already listed in previous code listings. Wondering about GetRows
of MyData
class? Here it is.
public IEnumerable<object> GetRows()
{
List<ListViewData> rows = new List<ListViewData>();
if (File.Exists("MyData.xml"))
{
var rowsFromFile = from c in XDocument.Load(
"MyData.xml").Elements(
"Data").Elements("Rows").Elements("Row")
select c;
foreach (var row in rowsFromFile)
{
rows.Add(new ListViewData(row.Element("col1").Value,
row.Element("col2").Value));
}
}
return rows;
}
We have covered code for most of the functionalities in the sample code. Before winding up, I would like to show you the MyData.xml.
="1.0" ="utf-8"
<Data>
<Rows>
<Row>
<col1>Susan</col1>
<col2>16</col2>
</Row>
<Row>
<col1>Mary</col1>
<col2>21</col2>
</Row>
<Row>
<col1>John</col1>
<col2>25</col2>
</Row>
<Row>
<col1>Mike</col1>
<col2>30</col2>
</Row>
</Rows>
</Data>
Conclusion
My goal was to create a sample application which allows the users to enter data into a ListView
with minimum key presses and confusion. I haven't used full capabilities of the ListView
and hence it is not a tutorial to learn WPF ListView
. However, if your target is efficient usage of ListView
, this helps. The code listing above doesn't cover all the code in the sample application. For a full listing, download the source code.
History
- November 17, 2010 - First version
- November 20, 2010 - Added more explanation and code listing