I am creating a WPF application to act as a front end for a video games library and I'm attempting to mimic the Netflix UI. One of the features in this application is to cycle through images of games to select which game you want to play.
The desired behavior is different than the behavior when arrowing through the items in a ListBox: when you arrow through items in a ListBox, your selection moves up and down. The behavior I'm looking to implement is that as you arrow through the items, the selected item is always at the first position and the items are cycling across the selector. The term for this would be a carousel where the selected item is at index 0.
I've implemented this poorly and to give some context, here's a picture of how my interface currently looks: My current implementation
To achieve this, I believe what I should do is extend the StackPanel class or maybe implement my own Panel. But details on custom panels are a bit complicated and hard to come by. I want to show what I've done to this point to get this working but I'm very unhappy with these implementations and I'd like to get some advice on what direction I should go for a proper implementation.
Here are some details on what I've tried.
The screenshot above is a result of a GameList class that I created which implements INotifyPropertyChanged and includes properties for 15 different games.
private GameMatch game0;
public GameMatch Game0
{
get { return game0; }
set
{
if (game0 != value)
{
game0 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game0"));
}
}
}
private GameMatch game1;
public GameMatch Game1
{
get { return game1; }
set
{
if (game1 != value)
{
game1 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game1"));
}
}
}
// identical code for games 2-10
private GameMatch game11;
public GameMatch Game11
{
get { return game11; }
set
{
if (game11 != value)
{
game11 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game11"));
}
}
}
private GameMatch game12;
public GameMatch Game12
{
get { return game12; }
set
{
if (game12 != value)
{
game12 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game12"));
}
}
}
I've laid the images out in my XAML and added enough so that they will run off the edge of the screen:
<StackPanel Name="GameImages" Orientation="Horizontal">
<Border BorderThickness="2" BorderBrush="AntiqueWhite">
<Image Name="Image_Game1" Source="{Binding CurrentGameList.Game1.FrontImage}"/>
</Border>
<Image Source="{Binding CurrentGameList.Game2.FrontImage}"/>
<!-- identical images for games 3-10 -->
<Image Source="{Binding CurrentGameList.Game11.FrontImage}" />
<Image Source="{Binding CurrentGameList.Game12.FrontImage}" />
</StackPanel>
I implemented a ListCycle class which can take any arbitrary list and a count of items that you want to cycle. In case it helps, here's the code for the ListCycle. It takes care of cycling the lists by tracking the index of items in list that should be displayed on screen in a given position.
public class ListCycle<T>
{
// list of games or whatever you want
public List<T> GenericList { get; set; }
// indexes currently available to display
// will cycle around the size of the generic list
public int[] indices;
public ListCycle(List<T> genericList, int activeCycleCount)
{
GenericList = genericList;
indices = new int[activeCycleCount];
InitializeIndices();
}
private void InitializeIndices()
{
if (GenericList != null)
{
int lastIndex = -1;
for (int i = 0; i < indices.Length; i++)
{
indices[i] = GetNextIndex(lastIndex);
lastIndex = indices[i];
}
}
}
private int GetNextIndex(int currentIndex)
{
currentIndex += 1;
if (currentIndex == GenericList.Count)
{
currentIndex = 0;
}
return currentIndex;
}
private int GetPreviousIndex(int currentIndex)
{
currentIndex -= 1;
if (currentIndex == -1)
{
currentIndex = GenericList.Count - 1;
}
return currentIndex;
}
public int GetIndexValue(int index)
{
return indices[index];
}
public T GetItem(int index)
{
return GenericList[indices[index]];
}
public void CycleForward()
{
for (int i = 0; i < indices.Length; i++)
{
if (i + 1 < indices.Length - 1)
{
indices[i] = indices[i + 1];
}
else
{
indices[i] = GetNextIndex(indices[i]);
}
}
}
public void CycleBackward()
{
for (int i = indices.Length - 1; i >= 0; i--)
{
if(i - 1 >= 0)
{
indices[i] = indices[i - 1];
}
else
{
indices[i] = GetPreviousIndex(indices[i]);
}
}
}
}
So when you press right, I cycle forward and reset the game images. When you press left, I cycle backward and reset the game images. The RefreshGames method takes care of updating all of those game properties in my game list.
private void RefreshGames()
{
Game0 = gameCycle.GetItem(0);
Game1 = gameCycle.GetItem(1);
Game2 = gameCycle.GetItem(2);
Game3 = gameCycle.GetItem(3);
Game4 = gameCycle.GetItem(4);
Game5 = gameCycle.GetItem(5);
Game6 = gameCycle.GetItem(6);
Game7 = gameCycle.GetItem(7);
Game8 = gameCycle.GetItem(8);
Game9 = gameCycle.GetItem(9);
Game10 = gameCycle.GetItem(10);
Game11 = gameCycle.GetItem(11);
Game12 = gameCycle.GetItem(12);
}
This approach works but it doesn't work well. It's not dynamic, it doesn't scale well and it doesn't perform all that well. Arrowing through images one at a time performs just fine but trying to quickly move through them, is a bit slow and feels clunky. It's not a very good user experience.
I tried a second approach, using a listbox bound the to my list of games. And to cycle the games to the left, I would remove the first item from my list of games and insert it at the end of the list. To go to the right, I would remove the item at the end of the list and insert it at index 0. This also worked but it didn't perform very well either.
So I'm looking for suggestions on a better way to implement this that would give better performance (smoother scrolling) and be more dynamic (i.e. this approach may not work well on an ultrawide monitor - 12 games may not be enough depending on the widths of the images). I'm not looking for anyone to solve it for me but point me in the right direction as I'm very new to WPF.
My feeling is that I should be extending the stack panel class and changing the way you cycle through the items or maybe creating my own panel. Can anyone confirm if this is the best approach and if so point me to some good resources to help me understand how to create a custom panel that changes the way navigation is done? I've been reading articles on creating custom panels to try to get my head around that process.
Being new to WPF, I want to make sure I'm not going down a rabbit hole or trying to reinvent a wheel that already exists. So the question is whether a custom panel is the right approach to solving this problem?
