2

I have a WPF application with a DataGrid (from WPF Toolkit) control. The ItemsSouce property is bound to an ObservableCollection in my ViewModel.

The data grid has a column with a TextBox in it:

<dg:DataGrid.Columns>
    <dg:DataGridTemplateColumn Header="Name">
         <dg:DataGridTemplateColumn.CellTemplate>
               <DataTemplate>
                   <TextBox Text="{Binding UserName}" Width="300"/>
                </DataTemplate>
         </dg:DataGridTemplateColumn.CellTemplate>
    </dg:DataGridTemplateColumn>
...

I also have an "Add" button to create a new user. When I click this, a new row is created. I would like, however, for the above textbox to get the input focus (on the new row of course). I have looked at:

WPF MVVM Focus Field on Load

WPF-MVVM: Setting UI control focus from ViewModel

How to set focus to textbox using MVVM?

Set focus on textbox in WPF from view model (C#)

But all of them seem to rely on same variation of an "ElementName" binding and none look like they would work in an ItemsControl. What is the correct way to get this behavior?

Community
  • 1
  • 1
BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • simply handle the TextBox's loaded event and do `Keyboard.Focus(sender as TextBox);` – Federico Berasategui Jun 24 '14 at 20:41
  • @HighCore Neat idea. On my first attempt that puts the focus on an existing text box, and adding new rows does not affect it. I'll try a few more versions though, thanks for the suggestion! *I noticed I was prepending, likely causing the problem* An order-independent solution would be nice if it is possible though. – BradleyDotNET Jun 24 '14 at 20:45
  • @HighCore, The insertion order was indeed causing the problem. Your solution is simple and effective (if order-dependent) and it would be a good answer. – BradleyDotNET Jun 24 '14 at 20:51

2 Answers2

3

One way I believe you can do this is to have a trigger on the textbox that handles on Loaded and have it set focus. Something like this

<dg:DataGridTemplateColumn Header="Name">
    <dg:DataGridTemplateColumn.CellTemplate>
       <DataTemplate>
           <TextBox xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" Text="{Binding UserName}" Width="300">
            <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Loaded">
                            <local:SetFocusTrigger/>
                        </i:EventTrigger>
                </i:Interaction.Triggers>
           </TextBox>
        </DataTemplate>
    </dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>

And the SetFocusTrigger class is :-

    public class SetFocusTrigger : TargetedTriggerAction<Control>
    {
        protected override void Invoke(object parameter)
        {
            if (Target == null) return;

            Target.Focus(); 
        }
    }

Note I haven't tried this out.

TYY
  • 2,702
  • 1
  • 13
  • 14
  • An interesting approach, seems like a no code-behind version of HighCore's solution. Is there a way to make this not dependent on where the new item is inserted in the list? – BradleyDotNET Jun 24 '14 at 20:50
  • I am surprised that there would be an issue with order, since when you add a new row, the loading should cause the new textbox to load and that in turn gets the trigger to act. The only other thing I can think of is to use the FocusManager.FocusedElement to set the correct Textbox. – TYY Jun 24 '14 at 21:06
  • Perhaps each text box gets reloaded as a result of the CollectionChanged event firing? How would your alternative work? – BradleyDotNET Jun 24 '14 at 23:04
  • yeah my alternate would not work unless you had some property that specifies which row should have the focus, if it is out of order. – TYY Jun 26 '14 at 15:17
  • Given that its a XAML only version of what I am actually doing, I'll accept yours as the answer. Thanks for the help! – BradleyDotNET Jun 26 '14 at 16:49
  • Working...Great – Colonel Software Sep 13 '19 at 07:05
0

I tried to do in pure XAML

<DataGrid CanUserAddRows="True"
          ItemsSource="{Binding List}"
          AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Name">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"
                               Width="300" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Name}"
                             FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
                             Width="300" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

I added two templates 1 for normal and other for edit mode. so once cell enter edit mode it will focus the text box.

if you always want the text box to be visible then it would have a different behavior as the new row is binded to placeholder and the value is text box will persist and move over to every new row but the real row.

ListView Solution (MVVM)

sample XAML

<StackPanel>
    <Button Command="{Binding AddItem}"
            Content="Add Item"/>
    <ListView ItemsSource="{Binding List}"
              Grid.IsSharedSizeScope="True">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition SharedSizeGroup="name" />
                        <!--other columns-->
                        <ColumnDefinition  />
                    </Grid.ColumnDefinitions>
                    <TextBox Text="{Binding Name}"
                             FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}" />
                    <TextBox Text="other column" Grid.Column="1"/>
                </Grid>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</StackPanel>

sample VM

    public ViewModel()
    {
        AddItem = new SimpleCommand(i => List.Add(new Person()));
        List = new ObservableCollection<object>(new[] { new Person() { Name = "a person"} });
    }

    public ObservableCollection<object> List { get; set; }

    class Person
    {
        public string Name { get; set; }
    }

    public ICommand AddItem { get; set; }

so upon adding new item the new row will be created by means of adding a new Person to the List and will be displayed in view with the focus on the text box.

run the sample above and see the behavior, no code behind involved.

pushpraj
  • 13,458
  • 3
  • 33
  • 50
  • This is interesting, how do I mark the cell for editing as soon as it is added? I do want them to always be editable as well. – BradleyDotNET Jun 25 '14 at 02:10
  • `grid.CurrentCell= newly added cell;` & `grid.BeginEdit();` will make the cell editable. – pushpraj Jun 25 '14 at 02:12
  • But I don't have access to the newly added cell... I just add an item to the bound ItemsSource. Sure I could search for it by index and such, but that seems way more complicated than the other answers (and is still order dependent) Is there some easy way to do this that I am missing? – BradleyDotNET Jun 25 '14 at 02:13
  • if you want all of them editable all the time, perhaps datagrid is not an option for you, you could use a plain items control with the template. and that will do the trick. let me try to come up with a solution for the same. – pushpraj Jun 25 '14 at 02:14
  • `ListView` might work (definitly NOT ItemsControl, as I need the "DataGrid" look) but my styles currently work on `DataGrid`. It allows them all to be editable anyways (perhaps a side effect of using the WPF Toolkit version?) Regardless, thats not really what the problem is. I'd be interested to see your ListView solution though, perhaps it will give me an idea. – BradleyDotNET Jun 25 '14 at 02:16