0

I want to visualize the progress of data transfers in a ListView. One line is one transfer containing a name and a state. The state of the transfer shall be displayed graphically. I want to draw a sequence of vertical lines. One line represents the state of a segment of the transfer, its color being the state of the segment (not transferred, error, transfer complete).

So, how do I model this in WPF? I thought of putting a WrapPanel into a DataTemplate inside a GridViewColumn.CellTemplate inside the GridViewColumn in the XAML. This is how far I came with web tutorials. Putting a single Line into a Canvas into the table also works, but as you see, I need it much more dynamic.

Now I need some data binding to put a varying number of Line shapes with fixed height and one pixel width and variable color into the WrapPanel. I thought of WrapPanel in order to automatically wrap that display around in case the window has less width than the number of segments need to be displayed.

Also: how do I tell the last column of the ListView to occupy the remaining space inside the window? I don't want a horizontal scroll bar.

The updates to all this come frequently, could be less than a second between two updates. So I don't want to redraw everything just because a single segment changes its color. So, how do I access a single Line in this situation?

Stefan Bormann
  • 643
  • 7
  • 25
  • Something like this? https://stackoverflow.com/a/40190793/1136211 – Clemens Aug 05 '19 at 12:06
  • I like the "abstract representation of a Shape (instead of a list of UI elements)", but I would prefer a more automatic placement of the Lines. And I don't understand that ItemsControl part - how does it know what kind of shape to render? – Stefan Bormann Aug 05 '19 at 13:53
  • By `` in the ItemTemplate. You could also make it more specific, like here: https://stackoverflow.com/a/22325266/1136211 – Clemens Aug 05 '19 at 13:59
  • I don't follow where geometries come into this at all. Aren't they all 1px wide rectangles? More "just" a horizontal listview a bit like https://i.imgur.com/3pC3yU6.png but with different colours and a wrappanel rather than a stackpanel to organise them? – Andy Aug 05 '19 at 18:54

1 Answers1

0

Okay, I finally accomplished what I wanted to see. First let's look into XAML. Here is only the part with the ListView and only the single column that contains the segments. Of course you need a window around this and can add more columns.

    <ListView Name="KnownImages" ItemsSource="{Binding FileViewModels}">
        <ListView.View>
            <GridView>

                <GridViewColumn Header="Segments" Width="100">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate DataType="local:FileViewModel">
                            <ItemsControl Width="auto" Height="auto" ItemsSource="{Binding Segments}">
                                <ItemsControl.ItemsPanel>
                                    <ItemsPanelTemplate>
                                        <WrapPanel/>
                                    </ItemsPanelTemplate>
                                </ItemsControl.ItemsPanel>
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate DataType="local:SegmentViewModel">
                                        <Line ToolTip="{Binding ToolTip}" X1="1" X2="1" Y1="{Binding YTop}" Y2="15" StrokeThickness="3" Stroke="{Binding Stroke}"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>

            </GridView>
        </ListView.View>
    </ListView>

The main window has a view model that contains

    public ObservableCollection<FileViewModel> FileViewModels { get; set; }

The ListView has a binding to that field. The DataTemplate has a view model FileViewModel that contains stuff for a single line in the list view that visualises one file. It mostly contains

public ObservableCollection<SegmentViewModel> Segments { get; set; }

So we can bind the ItemsControl to that field Segments. The WrapPanel inside the ItemsPanelTemplate is now populated with a DataTemplate that knows its data type to be SegmentViewModel:

/// <summary>
/// View model of a single file segment, which is represented with
/// a vertical line. The changeable parts of the line are stored here
/// and are directly derived from the state of the segment in the
/// constructor (ToolTip, Color and the upper Y coordinate of the line)
/// </summary>
public class SegmentViewModel
{
    private const int YTopEof=0;
    private const int YTopIntermediate=3;

    public string ToolTip { get; set; }
    public Brush Stroke { get; set; }
    public int YTop { get; set; }

    /// <summary>
    /// Derive appearance of the line from the business data received from queue
    /// if we already received data for the segment.
    /// </summary>
    /// <param name="fromQ">our knowledge about the segment state</param>
    public SegmentViewModel(SegmentState fromQ)
    {
        var sb = new StringBuilder();
        sb.Append("Seg ");
        sb.Append(fromQ.Number);
        if (fromQ.IsInMemory)
            sb.Append(", in memory");
        if (fromQ.IsOnDisk)
            sb.Append(", on disk");
        if (!fromQ.IsComplete)
            sb.Append(", INCOMPLETE!!!");

        ToolTip = sb.ToString();

        Color col;
        if (!fromQ.IsComplete)
            col = Colors.Magenta;
        else if (fromQ.IsInMemory)
            col = fromQ.IsOnDisk ? Colors.Black : Colors.Green;
        else
            col = fromQ.IsOnDisk ? Colors.RoyalBlue : Colors.Brown;

        Stroke = new SolidColorBrush(col);

        YTop = fromQ.EndOfFile ? YTopEof : YTopIntermediate;
    }

    /// <summary>
    /// This constructor is for segments that we did not receive so far.
    /// </summary>
    /// <param name="segmentNumber">We only know that we expect data from this index.</param>
    public SegmentViewModel(int segmentNumber)
    {
        ToolTip = $"Seg {segmentNumber} not yet seen";
        YTop = YTopIntermediate;
        Stroke = new SolidColorBrush(Colors.LightGray);
    }

For each instance of SegmentViewModel in the Segments array we get a new instance of Line as visualization. The variable visual properties are mapped with bindings (tooltip, color, line length). The non variable visual properties are constants in XAML. Note: the coordinates are not pixels but something smaller - at least on my screen.

Stefan Bormann
  • 643
  • 7
  • 25