如何解决如何在WPF中实现图像轮播,其中所选项目始终是第一个项目?
我正在创建WPF应用程序以充当视频游戏库的前端,并且试图模仿Netflix UI。此应用程序的功能之一是在游戏的图像之间循环,以选择要玩的游戏。
所需的行为与在ListBox
中的项目上箭头移动时的行为不同:当在ListBox
中的项目中箭头移动时,选择会上下移动。我要实现的行为是,当您在各个项目之间箭头导航时,所选项目始终位于第一个位置,并且各个项目在选择器之间循环。术语是轮播,其中所选项目的索引为0。
我没有很好地实现这一点,并提供了一些背景信息,这是我的界面当前外观的图片: My current implementation
要实现这一目标,我相信我应该做的是扩展StackPanel
类或实现我自己的Panel
。但是自定义面板上的细节有些复杂且难以掌握。我想展示为达到这一目的我已经做了些什么,但是我对这些实现感到非常不满意,我想就正确的实现应该朝哪个方向寻求一些建议。
以下是我尝试过的一些细节。
上面的屏幕截图是我创建的GameList
类的结果,该类实现了INotifyPropertyChanged
并包含15种不同游戏的属性。
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"));
}
}
}
我已经将图像布置在XAML中,并添加了足够的图像,以使它们可以从屏幕边缘移走:
<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>
我实现了一个ListCycle
类,该类可以接受任意列表和要循环的项目数。如果有帮助,这里是ListCycle
的代码。通过跟踪列表中应该在给定位置显示在屏幕上的项目的索引,它可以使列表循环。
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]);
}
}
}
}
因此,当您按向右键时,我向前循环并重置游戏图像。当您按向左键时,我向后循环并重置游戏图像。 RefreshGames方法负责更新我的游戏列表中的所有这些游戏属性。
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);
}
此方法有效,但效果不佳。它不是动态的,不能很好地扩展,并且不能很好地执行。一次浏览一个图像效果不错,但是尝试快速浏览它们,有点慢并且感到笨拙。这不是很好的用户体验。
我尝试了第二种方法,使用绑定到我的游戏列表的列表框。要将游戏循环到左侧,我将从游戏列表中删除第一项,并将其插入列表的末尾。要移到右边,我将删除列表末尾的项目,并将其插入索引0。这也可以,但是效果也不好。
因此,我正在寻找实现此方法的建议,以提供更好的性能(更流畅的滚动)和更动态的效果(即,这种方法在超宽显示器上可能无法很好地工作-12局游戏可能不够用,具体取决于在图像的宽度上)。我并不是在寻找任何人为我解决问题,而是因为我对WPF非常陌生,因此将我指向正确的方向。
我的感觉是,我应该扩展堆栈面板类,并更改在项目之间循环的方式,或者可能要创建自己的面板。任何人都可以确认这是否是最佳方法,如果可以,请给我指向一些好的资源,以帮助我了解如何创建自定义面板来更改导航的方式?我一直在阅读有关创建自定义面板的文章,以设法使自己对此过程有所了解。
作为WPF的新手,我想确保我不会掉进兔子洞或尝试重新发明已经存在的轮子。那么问题是,自定义面板是否是解决此问题的正确方法?
解决方法
我相信我应该做的是扩展
StackPanel
类
WPF encourages构成现有Controls
的继承;在您的情况下,继承StackPanel
对于您的目的而言似乎太复杂了,而您可以通过第二种方法实现同样的目的:
我将从游戏列表中删除第一项,并将其插入列表的末尾
这确实看起来更像是惯用的WPF,尤其是如果您尝试遵循MVVM设计模式。
或者创建自己的面板
这不是一个简单的步骤,特别是如果您不熟悉WPF,但这对您来说将非常有趣。那可能是一条路,特别是如果您内部依靠StackPanel
(组成)而不是从其继承(继承)。
使用ItemsControl
我将使用ItemsControl
来为您显示一组数据(在您的情况下,您有一些GameMatch
)。
首先定义接口背后的数据,即GameMatch
的集合。让我们给每个GameMatch
一个名称和一个变量IsSelected
,该变量告诉游戏是否被选中(即处于第一位置)。我没有显示INotifyPropertyChanged
的实现,但是两个属性都应该存在。
public class GameMatch : INotifyPropertyChanged {
public string Name { get => ...; set => ...; }
public bool IsSelected { get => ...; set => ...; }
}
您的轮播界面对GameMatch
的集合感兴趣,所以让我们创建一个对象对此进行建模。
我们的图形界面将绑定到Items
属性以显示游戏集合。
它也将绑定到实现的两个commands上,例如将列表向左或向右移动。您可以使用RelayCommand
创建命令。简而言之,Command
只是一个要执行的动作,您可以轻松地从您的界面中引用它。
public class GameCollection {
// Moves selection to next game
public ICommand SelectNextCommand { get; }
// Moves selection to previous game
public ICommand SelectPreviousCommand { get; }
public ObservableCollection<GameMatch> Items { get; } = new ObservableCollection<GameMatch> {
new GameMatch() { Name = "Game1" },new GameMatch() { Name = "Game2" },new GameMatch() { Name = "Game3" },new GameMatch() { Name = "Game4" },new GameMatch() { Name = "Game5" },};
public GameCollection() {
SelectNextCommand = new RelayCommand(() => ShiftLeft());
SelectPreviousCommand = new RelayCommand(() => ShiftRight());
SelectFirstItem();
}
private void SelectFirstItem() {
foreach (var item in Items) {
item.IsSelected = item == Items[0];
}
}
public void ShiftLeft() {
// Moves the first game to the end
var first = Items[0];
Items.RemoveAt(0);
Items.Add(first);
SelectFirstItem();
}
private void ShiftRight() {
// Moves the last game to the beginning
var last = Items[Items.Count - 1];
Items.RemoveAt(Items.Count - 1);
Items.Insert(0,last);
SelectFirstItem();
}
}
这里的键是ObservableCollection
类,它将在视图更改时(例如,每次我们在其中移动项目时)告诉视图,因此视图将更新以反映该视图。
然后,视图(您的XAML)应指定如何显示游戏集合。我们将使用ItemsControl
水平放置项目:
<StackPanel>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10" Background="Beige" BorderBrush="Black" Width="150" Height="50">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="true">
<Setter Property="BorderThickness" Value="5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Name}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Previous" Command="{Binding SelectPreviousCommand}"/>
<Button Content="Next" Command="{Binding SelectNextCommand}"/>
</StackPanel>
</StackPanel>
请注意,ItemsControl ItemsSource="{Binding Items}"
告诉ItemsControl
显示Items
属性中的所有对象。 ItemsControl.ItemsPanel
部分告诉您将它们放置在水平StackPanel
中。 ItemsControl.ItemTemplate
部分说明了如何显示每个游戏,DataTrigger
中的内容告诉WPF增加所选项目的边框厚度。最后,底部的StackPanel
显示两个Button
,它们分别在我们的SelectPreviousCommand
中调用SelectLeftCommand
和GameCollection
。
最后,您应该将整个内容的DataContext
设置为新的GameCollection
:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new GameCollection();
}
}
您可以从那里自定义UI。
动画和平滑滚动
这是另一个主题,例如,您可以在单击其中一个按钮时触发所有项目的翻译动画。
,我会尝试为您指明正确的方向。如果您尚未签出,我将尝试使您的应用程序遵循MVVM模式。在您的情况下,ViewModel的ObservableCollection
为“游戏”。然后,您将ItemsControl
的源绑定到该集合。
就轮播工作而言,我认为您想走的路是创建自定义ItemsControl
或ListBox
。您可以覆盖样式并创建一些自定义行为,以使轮播按照您的意愿进行工作。
如果您有更具体的问题,我可能会提供更多帮助。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。