Kris Selbekk
Kris Selbekk

Reputation: 7624

Back-button gets focused on list manipulation

I am creating a ListView that can have some items removed from it (it's a list of favorites) by selecting them, bringing up the app bar and clicking "Remove from favorites". When the button is clicked, a method in the current view model is asked to remove this item from the list. After this happens, the UI gets updated, and the item is removed.

Now, I have two problems. The first one is that the back-button of the page receives the focus (it gets a dotted outline) when an item is removed, something which I do not want.

The second problem is that the list doesn't use the add / delete animation I've set it to use.

A solution to either of these would be appreciated.

Here is some pseudo code showing what happens:

XAML:

<GridView x:Name="FavoritesGridView"
    Grid.Row="1"
    SelectionMode="Multiple"
    ItemTemplate="{StaticResource FavoritesOnSectionViewItemTemplate}"
    ItemsSource="{Binding FavoritesList}" 
    ItemClick="ProgramGrid_OnItemClick" 
    IsItemClickEnabled="True"
    SelectionChanged="FavoritesGridView_OnSelectionChanged"
    ScrollViewer.HorizontalScrollMode="Disabled">
    <GridView.ItemContainerStyle>
      <Style TargetType="Control">
        <Setter Property="Margin" Value="0,0,38,8"/>
      </Style>
    </GridView.ItemContainerStyle>
    <GridView.ItemContainerTransitions>
      <TransitionCollection>
        <AddDeleteThemeTransition/>
      </TransitionCollection>
    </GridView.ItemContainerTransitions>
    <GridView.ItemsPanel>
      <ItemsPanelTemplate>
        <WrapGrid Orientation="Vertical" MaximumRowsOrColumns="9" />
      </ItemsPanelTemplate>
    </GridView.ItemsPanel>
</GridView>

Codebehind:

private void UnFavoriteButton_Click(object sender, RoutedEventArgs e)
{          
    viewModel.RemoveFromFavorites(FavoritesGridView.SelectedItems.Cast<FavoriteProgram>().AsEnumerable());
}

ViewModel:

public void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs)
{
    FavoriteController.RemoveFromFavorites(programs);
    UpdateUi();
}

private void UpdateUi()
{
    OnPropertyChanged("FavoritesList");
}

public IEnumerable<FavoriteProgram> FavoritesList
{
    get { return CoreData.TvFavorites; } // A centralized list
}

FavoritesController:

public static void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs)
{
    if (programs.IsNullOrEmpty()) return;
    foreach (var program in programs)
        RemoveFromFavorites(program);
}

public static void RemoveFromFavorites(FavoriteProgram program)
{
    if (!IsFavorite(program)) return;

    var list = CoreData.TvFavorites.ToList();
    list.Remove(program);
    CoreData.TvFavorites = list.AsEnumerable();
}

Any ideas?

Upvotes: 4

Views: 399

Answers (2)

Jerry Nixon
Jerry Nixon

Reputation: 31813

I see. So you have two problems.

[1]. The back button receives focus.

It is my opinion that the Back button should never receive focus. There is already a key gesture to go back, so setting focus is silly. Why it wasn't already disabled to have focus, I do not know. Here's all you do:

<Button TabIndex="-1" Style="{StaticResource BackButtonStyle}" />

Or you can do it with a style:

<Grid Background="Black">
    <Grid.Resources>
        <Style TargetType="Button" BasedOn="{StaticResource BackButtonStyle}" x:Name="MyBackButtonStyle">
            <Setter Property="TabIndex" Value="-1" />
        </Style>
    </Grid.Resources>
    <Button Style="{StaticResource MyBackButtonStyle}" />
</Grid>

Using this new style (or just updating the existing one) will result in the back button never receiving focus. If you want it to be able to receive focus, for some reason, then the solution would be to handle the GotFocus event and simply use (sender as Button).Focus(FocusState.Unfocused);. To be fair, you should also determine why you would be removing focus.

[2]. The animations are not happening

This is a common problem. The reason is, you do not want to setup animations on the ListView, you want to set up the animations on the ListView ItemsPanel. Here's all you want to do:

<ListView>
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel>
                <StackPanel.ChildrenTransitions>
                    <AddDeleteThemeTransition />
                </StackPanel.ChildrenTransitions>
            </StackPanel>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
</ListView>

It's as simple as that. (my sample is a StackPanel, remember to use a WrapGrid as you have in your code) You just had the transitions in the wrong place. So, now you can handle the focus problem you are having and you can get the transitions you want.

I might offer a bit of advice. Since you are using view models, it sounds strange to hear that you are not also using delegate commands. If you want to use MVVM at its best, delegate commands solve a lot of problems for you. Read here: http://blog.jerrynixon.com/2012/08/most-people-are-doing-mvvm-all-wrong.html

And for a second bit of advice. It sounds to me like you might be using the default templates from Visual Studio. Most developers start there. The problem is that those templates are not very great to teach best practices. My suggestion: don't be afraid of the blank template.

Best of luck!

Upvotes: 3

Jehof
Jehof

Reputation: 35544

Now, I have two problems. The first one is that the back-button of the page receives the focus (it gets a dotted outline) when an item is removed, something which I do not want.

This problem could be solved, by adding a TwoWay-Binding to the SelectedItem property of the GridView. After removing the favorite programms set the SelectedItem per code, so that it gets focused in the GridView.

XAML:

<GridView x:Name="FavoritesGridView"
          SelectedItem="{Biding SelectedFavorite, Mode=TwoWay}" />

C#:

private FavoriteProgram _selectedFavorite;

public FavoriteProgram SelectedFavorite {
  get {
    return _selectedFavorite;
  }
  set {
    _selectedFavorite = value;
    OnPropertyChanged("SelectedFavorite");
  }
}

After removing your items, set the property SelectedFavorite to an item in your FavoritesList.

public void RemoveFromFavorites(IEnumerable<FavoriteProgram> programs) {
  FavoriteController.RemoveFromFavorites(programs);

  UpdateUi();

  SelectedItem = FavoritesList.FirstOrDefault(); // selects the first element in list.
}

The second problem is that the list doesn't use the add / delete animation I've set it to use.

The problem here is that you always use a new collection/list for your property CoreData.TvFavorites after you have removed your favorites and therefore can the GridView not determine which items have been removed or added.

For binding scenarios there is a specialized collection named ObservableCollection<T>, that implements the interface INotifyCollectionChanged. The interface defines an event to notify (UI Elements) that items are added or removed from the collection. You should change your property FavoritesList to type ObservableCollection<FavoriteProgramm> and update the collection in your UpdateUI method to remove the relevant favorites.

private void UpdateUi()
{ 
   //Update your FavoritesList to enable animations.
    OnPropertyChanged("FavoritesList");
}

private ObservableCollection<FavoriteProgram> _favorites;
public ObservableCollection<FavoriteProgram> FavoritesList
{
  get { 
    if (_favorites == null) {
      _favorites = new ObservableCollection<FavoriteProgram>();
    }

    return _favorites; 
  }
}

Upvotes: 2

Related Questions