Editing In ListView
ListView does not support editing in V1, but for those who are familiar with File Explorer, editing is quite useful. I will introduce how to make a cell editable in ListView. Furthermore, you will find it is easy to build a custom control.
However, it is just a sample to enable simple editing in ListView. The target scenarios of ListView are displaying items not editing. As for those who have intensive requirements for editing, ListView control may be not a good choice, because we are not sure how far you can go.
Besides, cell keyboard navigation is not enabled.
Idea
To achieve our goal, create a customized control called EditBox for GridViewColumn.CellTemplate. The EditBox has two modes, one for displaying content in normal way, the other one for displaying content in editable mode. In normal mode, the EditBox displays content in a TextBlock, when in editable mode, it displays content in a TextBox, which lies in the TextBlock's adorner
1. Create a customized Edit Control
To build an EditBox, we need to define its OM(object model) and behaviors first. In our context, the OM can be outlined by the following rules:
- When the ListViewItem that contains an EditBox is selected, click on the EditBox should switch it into Editing mode.
- When in editing mode, pressing Enter key or F2 can switch it into Normal mode.
- When in editing mode, once the TextBox loses its focus, the EditBox should switch to its Normal model.
Behaviors:
- In Normal mode, a TextBlock is used to display content;
- In Editing mode, a TextBox will pop up for editing.
Assumptions
This control is built for a DataTemplate to specify GridViewColumn.CellTemplate. No guarantee that it can work out of GridView.
Steps
1) Create an EditBox inherited from Control. Since we need a property to store displayed value, we add a dependency property “Value”.
public class EditBox : Control
{
static EditBox()
{
......
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(object),
typeof(EditBox),
new FrameworkPropertyMetadata());
......
}
2) Add a DependencyProperty to represent the mode: IsEditing. It is of type Boolean, when it is true, EditBox is in Editing mode. We use it to trigger whether to show the TextBox or not.
......
public static DependencyProperty IsEditingProperty =
DependencyProperty.Register(
"IsEditing",
typeof(bool),
typeof(EditBox),
new FrameworkPropertyMetadata(false)
);
......
3) Style the EditBox.
<Style x:Key="{x:Type l:EditBox}" TargetType="{x:Type l:EditBox}" >
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type l:EditBox}">
<TextBlock x:Name="PART_TextBlockPart"
Text="{Binding Path=Value, RelativeSource = {RelativeSource TemplatedParent}}">
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
4) To enable editing, create EditBoxAdorner to host the TextBox. It needs to do some measuring and arranging work to make TextBox work as expected.
internal sealed class EditBoxAdorner : Adorner
{
/// <summary>
/// Inialize the EditBoxAdorner.
/// </summary>
public EditBoxAdorner(UIElement adornedElement, UIElement adorningElement)
: base(adornedElement)
{
_textBox = adorningElement as TextBox;
Debug.Assert(_textBox != null, "No TextBox!");
_visualChildren = new VisualCollection(this);
BuildTextBox();
}
......
5) To implement all the rules, add some event handling code.(See source code for details)
......
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
if (!IsEditing && IsParentSelected)
{
_canBeEdit = true;
}
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
_isMouseWithinScope = false;
_canBeEdit = false;
}
......
2. Customize CellTemplate
By putting the EditBox in GridViewColumn.CellTemplate, the column can be edited when its ListViewItem is selected and then clicked. For example:
......
<GridViewColumn Header="Month" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<l:EditBox Value="{Binding Month}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn />
......
Conclusion: To implement editing in GridView, you need to build a customized control that can switch between different modes(Editing and Normal), and then put the control into GridViewColumn.CellTemplate. Here I just demonstrate the idea. You may change it to build a more powerful control that can be used in wider situations.
This sample is based on the February CTP.
Declaimer: This posting is provided "AS IS" with no warranties, and confers no rights.
Comments
- Anonymous
March 27, 2006
I downloaded your example and found the arrow-down not working all the times in the ListView:
After I edited an entry and scroll around in the ListView using the keyboard arrow keys they suddenly stop working (at the same place everytime). I can see nothing in your source to have a clue what is happening here. - Anonymous
March 27, 2006
I have fixed the problem and updated the zip file.
Thanks for your feedback. - Anonymous
March 27, 2006
The comment has been removed - Anonymous
March 27, 2006
For the first question, you can override the OnKeyDown event handler on EditBox to do this, you can do this based on the sample.
For the second one, you need to add a new DependencyProperty onto the EditBox, for example, IsEditingForFirstTiem. Every time the code to change IsEditing in EditBox, do more logic judement to set the EditBox's status.
We come up with the example to demostrate how to implement Editing in current Listview and don't want to make the example too complicated. If any problem, please contact us.
Thanks. - Anonymous
March 27, 2006
Your example is great!
I am just trying to achieve some more things which are not easy for me to do.
My problem: I bound the ListView.ItemsSource to a DataTable. There is a button to insert a new Row into the DataTable (and therefore into the data-bound ListView as well).
Now this button does add an 'new item' to the DataTable, does set the SelectedIndex of the ListView to the new item, and then would like put the user into editing the field.
But I got no idea how to do this from Windows1.xaml.cs. I found nothing to achieve editing in the ListView, and I can't access the EditBox from the Window.
Probably I'm missing something very obvious?
Thanks again! - Anonymous
March 28, 2006
You can try this way:
1. Add a DependencyProperty on EditBox, let say, CanChangeToEditing, and bind it to Tag property on the ListViewItem that contains it. In its PropertyChanged callback function, set IsEditing to true.
2. When a new item is added into the Listview, you get the ListViewItem and set its Tag as value true(a bool value).
Thanks.
- Anonymous
March 29, 2006
I just noticed: changes made in this EditBox are shown on screen, but not stored in the ItemsSource.
I just added a button to dump the ItemsSource like this:
void Dumpdata_Click(object sender, RoutedEventArgs e)
{
foreach (object o in Liste.ItemsSource)
System.Diagnostics.Trace.WriteLine(o);
}
And no matter what I change in the EditBox, nothing does happen to the data itself.
My guess is _textbox needs to update the binding somewhere around EditBox.cs(167), I just don't know yet how to achieve this. - Anonymous
March 29, 2006
For your this requirement, I think the current ListView or EditBox can not do this for you: ListView or EditBox can not predict what kink of data source you will use, not to mention how to change the data source, maybe you need do much input verification work before updating the datasource, maybe you need to implement much commercial logic before changing the data source.
So, it should be responsibility of the author of application to do this.
As I mentioned before, we don't want to make this sample too complicated, so we will not implement this in the sample. But you can try those ways:
1. Add a PropertyChanged callback on the DependencyProperty Value, you can do something in this callback funciton to update your datasource, you can update your datasource directly here, or you can raise a RoutedEvent for ListView to hook.
2. There is AddSourceUpdatedHandler in Binding, so you can hook event handlers to update your datasource.
Thanks for your feedback.
- Anonymous
March 29, 2006
Continue last reply:
If your data item is not readonly, I means it can be written, you can set Mode=Twoway on the DisplayMemberBinding. For example:
DisplayMemberBinding="{Binding DataField, Mode=Twoway}"
This is the simplest way to do this, but there is not any invalidation here.
Thanks. - Anonymous
March 29, 2006
Oh, and I thought not needing to know what kind of data source you use is exactly the point of data binding?
And I really appreciate you to keep the sample as simple as possible, but still I'd say an example should work, should it not?
Honestly I am disappointed. You write about being able to edit in a ListView, but when it does not work you try funky excuses instead of either saying 'I can't/won't make this work' or showing a working sample.
Instead you add disclaimers, provide fixes that do not work (did you test Twoway binding?) and post excuses (honestly, data checking can be done later, or with some other hook).
The whole point of an example is to show something. Either to show a way to achieve something, or show how something is not done.
What good is an example that does not work????
It neither tells me how something is done, nor what I should not do.
Maybe I understood the point of this all wrong, or you lost me somewhere along the way.
Either way I am rather disappointed - not only with the sample, with your reaction, too.
Of course I got no right for a working sample, and I'd be perfectly happy if you say 'sorry, I dont want to put more time in this to get a real sample', but stuff like 'want to keep it simple' as excuse for it not working is a joke. - Anonymous
March 29, 2006
Well, I got a bit of progress (actually two steps forward and one step back).
Bindings:
1) MyData is bound to EditBox.Value
2) EditBox.Value is bound to TextBlock.Text
3) TextBlock.Text is bound to TextBox.Text
1) and 2) are OneWay-Bindings. When I set both of them to Mode=Twoway changes get propagated to MyData.
BUT Editing immediately ends with every change since databinding reports those changes back to my control. A whole different problem, but at least a change. - Anonymous
March 29, 2006
The comment has been removed - Anonymous
March 29, 2006
Yes, you are doing with the right way. I am sorry that I did not make it clear in my last reply.
Do you mean that every time you change the content in EditBox, editing ends?
parent.SizeChanged += OnCouldSwitchToNormalMode; is to close editing when someone resizes its column.
_textBox.LostKeyboardFocus += OnTextBoxLostKeyboardFocus;
is to close editing when EditBox loses focus.
if you have special requirement, you can changes those event handlers to implement your purpose.
- Anonymous
March 29, 2006
Let me make sure you changed the code as I did:
1, In the style of EditBox, chage Text="{Binding Path=Value, RelativeSource = {RelativeSource TemplatedParent}}"> to
Text="{Binding Path=Value,Mode=Twoway, RelativeSource = {RelativeSource TemplatedParent}}">
2, In the listView, use
DisplayMemberBinding="{Binding DataField, Mode=Twoway}"
This works well on my machine.
Thanks. - Anonymous
March 29, 2006
Yes, these are my changes, too.
I've bound to a DataTable, and in my case it does end the editing on every keystroke in the EditBox.
What kind of data collection did you use? - Anonymous
March 30, 2006
For convenience, I just used ObservableCollection<GridViewColumn> as data source. But I think it should not impact the editing status.
You can set a breakpoint in the setter funciton of IsEditing, or every places that changing IsEditing as false, when the EditBox ends on a keystroke, you can figure out easily what results in its closure.
Thanks. - Anonymous
March 30, 2006
I've just changed the example to use an ObservableCollection<> and it works, yes.
Changed it back to a strongly typed DataTable, and it quits editing on every keypress.
So the problem must be somewhere around the DataTable - I won't go deeper, I'll just use an ObservableCollection.
Thanks for the help! - Anonymous
September 24, 2006
The comment has been removed - Anonymous
September 27, 2006
You can try the following way:
Since the problem is that text block can not measure as you expected when the text is exmpty, you could do some changes in the method MeaserOverride to fix your problem, for example, set a fixed value when the width is smaller than a critical value, and so like.
Let us know if you have any problem, please also give us your build informtion.
Thanks. - Anonymous
November 06, 2006
The comment has been removed - Anonymous
November 06, 2006
Sorry, I did not describe it clearly. Actually, the easist way to fix this problem is to add MinWidth and MinHeight properties onto TextBlock: <TextBlock MinWidth ="" MinHeight ="" x:Name="PART_TextBlockPart" Text="{Binding Path=Value,RelativeSource = {RelativeSource TemplatedParent}}"> </TextBlock> in Windows1.xaml file.As for the MeasureOverride way, I meant that you could add more logic into EditBoxAdorner.MeasureOverride , which locates in EditBoxAdorner.cs. - Anonymous
April 25, 2007
About the editing ending on every keystroke when bound to a DataTable that was bugging Radeldudel over a year ago - I cannot vouch for the only-on-DataTable aspect but I commented out the "HookTemplateParentResizeEvent()" call in "EditBox.OnApplyTemplate()" and the problem went away for me with no observable ill effects.Note that I picked up the code recently from the WPFSamples.exe distribution where the EditBox.cs and etc. are dated early October so I don't know if the code has changed since the original post. - Anonymous
May 24, 2007
When can we expect something more complete than just a sample application that hardly behaves the way you expect it to?Are there any alternatives out there? - Anonymous
September 14, 2007
PingBack from http://jaap.eljakim.nl/blog/2007/09/15/wpf-listview