如何以编程方式在C#中创建WPF可滚动StackPanel列表

时间:2018-01-17 23:11:16

标签: c# wpf listview dynamic

我的情况:

我正在开发一个C#WPF应用程序(在Windows上),我需要在运行时中动态创建很多控件。由于应用程序的性质,我无法在WPF窗口的许多方面使用标准XAML(带有模板)。这是一个非常独特的案例,不,我不会重新考虑我的申请格式。

我想要完成的任务:

我想以编程方式创建一个显示StackPanel s(或任何其他有效控制组)的可滚动列表的控件,对于一个用例,每个控件组都包含一个Image控件(标题/标题)顶部的TextBlock控件(图片):

  • 我更愿意在没有任何数据绑定的情况下完成所有(请参阅下面的推理)。因为这些项是在运行时定义的,所以我应该能够通过迭代在没有它们的情况下完成这些。
  • 控件/查看器应该能够有多个列/行,因此它不是一维的(就像典型的ListBox控件一样)。
  • 它也应该可以互换,以便您可以修改(添加,删除等)控件中的项目。

我已经包含了一张图片(下方),为您提供了一个可能的用例示例。

past中,我已经能够使用ListView使用ItemTemplate(包裹在ScrollViewer中)使用XAML来完成所有这些操作。但是,完全使用C#代码执行此操作会使其更加困难。我最近使用普通的c#代码ControlTemplate进行了FrameworkElementFactory s。它可能会有点复杂,我不确定它是不是最好的做法。我应该尝试去做同样的事情。路由(使用ListView和模板)?如果是这样,怎么样?或者是否有一个更简单,更优雅的选项来实现C#代码?

enter image description here

编辑:我真的更喜欢来使用任何数据绑定。我只想创建一个StackPanels的(可滚动的)列表,我可以轻松地修改/调整它。使用数据绑定感觉就像是向后实现,并且违背了运行时动态特性的目的。

编辑2(1/25/2018):响应不多。我只需要一个统一的,可滚动的stackpanel列表。我可以调整它以满足我的需求,但它需要全部用C#(代码隐藏)。如果有人需要更多信息/澄清,请告诉我。感谢。

LINK TO XAML POST

1 个答案:

答案 0 :(得分:3)

以下是使用ListBox UniformGrid作为ItemsPanelTemplate在代码中执行此操作的方法。或者,你只能使用UniformGrid并将其放在ScrollViewer内,但由于ListBox已经处理了选择和所有这些内容,你可能最好坚持使用那个。此代码将根据可用宽度自动调整行中的项目数。

MoviePresenter.cs:

public class MoviePresenter : ListBox
{
    public MoviePresenter()
    {
        FrameworkElementFactory factory = new FrameworkElementFactory(typeof(UniformGrid));
        factory.SetBinding(
            UniformGrid.ColumnsProperty,
            new Binding(nameof(ActualWidth))
            {
                Source = this,
                Mode = BindingMode.OneWay,
                Converter = new WidthToColumnsConverter()
                {
                    ItemMinWidth = 100
                }
            });

        ItemsPanel = new ItemsPanelTemplate()
        {
            VisualTree = factory
        };
    }
}

internal class WidthToColumnsConverter : IValueConverter
{
    public double ItemMinWidth { get; set; } = 1;

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double? actualWidth = value as double?;
        if (!actualWidth.HasValue)
            return Binding.DoNothing;

        return Math.Max(1, Math.Floor(actualWidth.Value / ItemMinWidth));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

MovieItem.cs:

public class MovieItem : Grid
{
    public MovieItem()
    {
        RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
        RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
        RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
        RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });

        Image image = new Image();
        image.Stretch = Stretch.UniformToFill;
        image.SetBinding(Image.SourceProperty, new Binding(nameof(ImageSource)) { Source = this });
        Children.Add(image);

        TextBlock title = new TextBlock();
        title.FontSize += 1;
        title.FontWeight = FontWeights.Bold;
        title.Foreground = Brushes.Beige;
        title.TextTrimming = TextTrimming.CharacterEllipsis;
        title.SetBinding(TextBlock.TextProperty, new Binding(nameof(Title)) { Source = this });
        Grid.SetRow(title, 1);
        Children.Add(title);

        TextBlock year = new TextBlock();
        year.Foreground = Brushes.LightGray;
        year.TextTrimming = TextTrimming.CharacterEllipsis;
        year.SetBinding(TextBlock.TextProperty, new Binding(nameof(Year)) { Source = this });
        Grid.SetRow(year, 2);
        Children.Add(year);

        TextBlock releaseDate = new TextBlock();
        releaseDate.Foreground = Brushes.LightGray;
        releaseDate.TextTrimming = TextTrimming.CharacterEllipsis;
        releaseDate.SetBinding(TextBlock.TextProperty, new Binding(nameof(ReleaseDate)) { Source = this });
        Grid.SetRow(releaseDate, 3);
        Children.Add(releaseDate);
    }

    public static readonly DependencyProperty ImageSourceProperty =
        DependencyProperty.Register("ImageSource", typeof(string), typeof(MovieItem), new PropertyMetadata(null));

    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(string), typeof(MovieItem), new PropertyMetadata(null));

    public static readonly DependencyProperty YearProperty =
        DependencyProperty.Register("Year", typeof(string), typeof(MovieItem), new PropertyMetadata(null));

    public static readonly DependencyProperty ReleaseDateProperty =
        DependencyProperty.Register("ReleaseDate", typeof(string), typeof(MovieItem), new PropertyMetadata(null));

    public string ImageSource
    {
        get { return (string)GetValue(ImageSourceProperty); }
        set { SetValue(ImageSourceProperty, value); }
    }

    public string Title
    {
        get { return (string)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }

    public string Year
    {
        get { return (string)GetValue(YearProperty); }
        set { SetValue(YearProperty, value); }
    }

    public string ReleaseDate
    {
        get { return (string)GetValue(ReleaseDateProperty); }
        set { SetValue(ReleaseDateProperty, value); }
    }
}

MainWindow.xaml:

<Grid>
    <local:MoviePresenter x:Name="moviePresenter" 
                          ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
</Grid>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        for (int i = 0; i < 20; i++)
        {
            DateTime dummyDate = DateTime.Now.AddMonths(-i).AddDays(-(i * i));

            MovieItem item = new MovieItem()
            {
                ImageSource = $"http://fakeimg.pl/100x200/?text=Image_{i}",
                Title = $"Dummy movie {i}",
                Year = $"{dummyDate.Year}",
                ReleaseDate = $"{dummyDate.ToLongDateString()}"
            };

            moviePresenter.Items.Add(item);
        }
    }
}