如何在WPF中实现图像轮播,其中所选项目始终是第一个项目?

如何解决如何在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中调用SelectLeftCommandGameCollection

最后,您应该将整个内容的DataContext设置为新的GameCollection

public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
        DataContext = new GameCollection();
    }
}

您可以从那里自定义UI。

final result

动画和平滑滚动

这是另一个主题,例如,您可以在单击其中一个按钮时触发所有项目的翻译动画。

,

我会尝试为您指明正确的方向。如果您尚未签出,我将尝试使您的应用程序遵循MVVM模式。在您的情况下,ViewModel的ObservableCollection为“游戏”。然后,您将ItemsControl的源绑定到该集合。

就轮播工作而言,我认为您想走的路是创建自定义ItemsControlListBox。您可以覆盖样式并创建一些自定义行为,以使轮播按照您的意愿进行工作。

如果您有更具体的问题,我可能会提供更多帮助。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-