简体   繁体   中英

How to programmatically create WPF scrollable StackPanel list in C#

My Situation:

I'm developing a C# WPF Application (on Windows) where I need to dynamically create a lot of my controls at runtime . Because of the nature of the application, I'm not able to use standard XAML (with Templates) for many aspects of my WPF windows. This is a very unique case, and no, I'm not going to reconsider the format of my application.

What I want to accomplish:

I would like to programmatically create a control that displays a scrollable list of StackPanel s (or any other effecient control group) which, for one use case, will each consist of an Image control (picture) on top of a TextBlock control (title/caption):

  • I would prefer to do all this without any data bindings (See below for reasoning). Because the items are being defined at runtime, I should be able to do this without them via iteration.
  • The control/viewer should be able to have multiple columns/rows, so it's not one dimensional (like a typical ListBox control).
  • It should also interchangeable so that you can modify (add, remove, etc.) the items in the control.

I've included a picture (below) to give you an example of a possible use case.

In the past , I have been able to accomplish all this by using a ListView with an ItemTemplate (wrapped in a ScrollViewer ) using XAML. However, doing this entirely with C# code makes it a bit more difficult. I've recently made ControlTemplate s in plain c# code (with FrameworkElementFactory s. It can get a bit complicated, and I'm not sure it's really the best practice. Should I try to go the same route (using a ListView with a template)? If so, how? Or is there a simpler, more elegant option to implement with C# code?

在此输入图像描述

Edit: I would really prefer not to use any data bindings. I just want to create a (scrollable) 'list' of StackPanels that I can easily modify/tweak. Using data bindings feels like a backwards implementation and defeats the purpose of the dynamic nature of runtime.

Edit 2 (1/25/2018): Not much response. I simply need a uniform, scrollable list of stackpanels . I can tweak it to suit my needs, but it needs to be all in C# (code-behind). If anyone needs more information/clarification, please let me know. Thanks.

LINK TO XAML POST

Here's a way to do it in code using a ListBox with UniformGrid as ItemsPanelTemplate . Alternatively, you can only use a UniformGrid and put it inside a ScrollViewer , but as the ListBox already handles selection and all that stuff, you probably better stick with that one. This code will automatically adjust the number of items in a row depending on the available width.

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);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM