Question

I need to write a Windows Form where the user can view many "contracts" grouped by "customer". Each customer must be a expand-collapse panel, and the contracts of the customer must be inside of the corresponding panel.

I already tried the great ExpandCollapsePanel, but when the number of customers is big then the panel doesn't autoscroll, even with the AutoScroll property set to true.

Does anybody know some other alternatives? Remember the panels must be dynamically created because there are many customers and many contracts belonging to each customers.

Thank you!

Was it helpful?

Solution

Ok, I've created a sample using an ElementHost to host a WPF UserControl, it looks like this:

enter image description here

I've uploaded the full source code Here, but anyways these are the most relevant parts:

Form1:

public partial class Form1 : Form
{
    public CustomerContractsViewModel ContractsVM { get; set; }

    public Form1()
    {
        InitializeComponent();

        ContractsVM  = new CustomerContractsViewModel();

        var customercontractsview = new CustomerContractsView(){DataContext = ContractsVM};

        var elementHost = new ElementHost() { Dock = DockStyle.Fill };
        elementHost.Child = customercontractsview;

        panel1.Controls.Add(elementHost);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        ContractsVM.LoadCustomers(DataSource.GetCustomers());
    }
}

(Designer code omitted for brevity)

WPF View:

<UserControl x:Class="ElementHostSamples.CustomerContractsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <!-- This style is applied to all Label elements within the UserControl-->
        <Style TargetType="Label">
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="HorizontalAlignment" Value="Right"/>
        </Style>

        <!-- This DataTemplate will be used to render the Contract items-->
        <DataTemplate x:Key="ContractTemplate">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>

                <Label Grid.Row="0" Grid.Column="0" Content="Contract Date:"/>
                <Label Grid.Row="1" Grid.Column="0" Content="Amount:"/>

                <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ContractDate, StringFormat='MM/dd/yyyy'}" VerticalAlignment="Center"/>
                <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Amount, StringFormat=C}" VerticalAlignment="Center"/>
            </Grid>
        </DataTemplate>

        <!-- This DataTemplate will be used to render the Customer Items -->
        <DataTemplate x:Key="CustomerTemplate">
            <Expander Header="{Binding Name}">
                <ListBox ItemsSource="{Binding Contracts}" ItemTemplate="{StaticResource ContractTemplate}">
                    <ListBox.Template>
                        <ControlTemplate TargetType="ListBox">
                            <ItemsPresenter/>
                        </ControlTemplate>
                    </ListBox.Template>
                </ListBox>
            </Expander>
        </DataTemplate>
    </UserControl.Resources>

    <ListBox ItemsSource="{Binding Customers}"
             ItemTemplate="{StaticResource CustomerTemplate}"/>
</UserControl>

Code Behind:

public partial class CustomerContractsView : UserControl
{
    public CustomerContractsView()
    {
        InitializeComponent();
    }
}

ViewModel:

public class CustomerContractsViewModel:PropertyChangedBase
{
    public List<Customer> Customers { get; set; }

    public void LoadCustomers(List<Customer> customers)
    {
        Customers = customers;
        OnPropertyChanged("Customers");
    }
}
  • Notice how this simple, less than 100 lines of code, 20-minute WPF sample is better than anything you can ever hope to achieve in winforms, and doesn't need any "owner draw", "P/Invoke" (whatever that means) or horrendous gargantuan code behind stuff. And does not force you to spend lots of money in third party components such as DevExpress or Telerik. This is why WPF is the best option for ALL .Net Windows Desktop application development, regardless if it's a simple Hello World type of stuff.

  • I'm using an ItemsControl to host the Customer items, and inside these I'm using a ListBox with a custom DataTemplate to show the Contract items.

  • Both ItemsControl (outer and inner) are Virtualized to enable an immediate response time, even with 200,000 items.

  • Notice that there's not a single line of code that interacts with the UserControls' UI Elements, Everything is defined in XAML and populated with data via DataBinding. This enables a great amount of scalability and maintainability because the UI is completely decoupled from the application logic / business logic. That's the WPF way.

  • The Form code (except for the initialization code) only interacts with the ViewModel, and has no need to interact with the WPF View.

  • When upgrading from winforms to WPF, you seriously need to embrace The WPF Mentality, which is, as mentioned before, you almost never manipulate UI elements in procedural code, or use too much code behind, but rather use DataBinding for everything and embrace The MVVM Pattern

  • WPF Rocks. Download the linked source code and see the results for yourself.

  • Let me know if you need further help.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top