Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Sample Code to Add, Edit and Delete Rows in a .NET WPF ListView

0.00/5 (No votes)
23 Nov 2010 1  
Sample code to add, edit and delete rows in a .NET WPF ListView

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.

ListViewTest screenshot

What It Does

This sample code does the following:

  1. Loads data from an XML file MyData.xml located in application folder.
  2. Allows the user to add, edit and delete rows to the listview.
  3. Saves the data back to MyData.xml when Save or OK button is clicked.

What It Doesn't Do

  1. This is just a sample code. You will need to customize it for your own requirements.
  2. 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.
  3. 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.
  4. 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
{
    /// <summary>
    /// Class to hold data in the ListView
    /// </summary>
    public class ListViewData
    {
        public ListViewData()
        {
            // default constructor
        }

        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 no rows left, add a blank row
    if (listView1.Items.Count == 0)
    {
        AddRow();
    }
    // otherwise select next row
    else if (selectedIndex <= listView1.Items.Count - 1) 
    {
        listView1.SelectedIndex = selectedIndex;
    }
    else // not above cases? Select last row
    {
        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"))
    {
        // Create the query 
        var rowsFromFile = from c in XDocument.Load(
                            "MyData.xml").Elements(
                            "Data").Elements("Rows").Elements("Row")
                                   select c;

        // Execute the query 
        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.

<?xml version="1.0" encoding="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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here