Question

I'm creating a graph control. what i'm doing for adding x and y axis tally labels is i'm adding a text block to each tally mark and show the value related to that tally mark.

but when i need to load data form the database and redraw the textbolcks again and refresh the graph area i can't remove the older textblocks they are still on the graph pane.

to overcome this problem i thought to put the text blocks in side a group box and when graph pane is redrawn to delete the group box elements and put them again..

is this approach correct? please tell me how to put elements to groupbox in code behind class? and please tell me if their is any other solution to my problem.

regards, rangana.

Was it helpful?

Solution

In WPF there are many solutions to most problems. I will discuss three possible solutions to your problem - the one you describe and two others. You can decide which will work best for you.

Solution 1: Using TextBlock objects to disply the labels

It sounds like you have a Canvas and you're adding a TextBlock to it for each tick mark. This is a viable solution if performance isn't too critical and you can't use data binding.

There are two ways to remove the TextBlocks in this case:

  1. You can keep a List<TextBlock> containing all the Textblocks list of the TextBlocks you created the last time you created the labels. Whenever you recreate the labels, run through this list and remove each TextBlock on the list from the containing panel (the Canvas)

  2. You can create a new Canvas and put the TextBlocks on it, then delete the whole Canvas when you relabel.

Here is an example of the second technique, since it is slightly more efficient:

class MyGraphBuilder
{
  Canvas _labelCanvas;
  ...
  void AddLabels()
  {
    // Remove old label canvas, if any
    if(_labelCanvas!=null)
      _graphCanvas.Children.Remove(_labelCanvas);

    // Add new label canvas
    _labelCanvas = new Canvas();
    _graphCanvas.Children.Add(_labelCanvas);

    // Create labels
    foreach(...)
    {
      ...
      _labelCanvas.Add(new TextBlock ...
    }
    ...

  }
}

Solution 2: Using data binding

In WPF you can create many graphs without writing a single line of code! WPF's built in data binding is sufficient to create relatively complex bar charts, etc.

Here is an example of using data binding to create a simple bar chart:

<ItemsControl ItemsSource="{Binding myData}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <DockPanel>
        <TextBlock Width="50" Text="{Binding Label}"/>
        <Rectangle VerticalAlignment="{Stretch}" Width="{Binding Value}">
          <Rectangle.LayoutTransform>
            <ScaleTransform ScaleX="10" /> <!-- Scale factor here, can be binding too -->
          </Rectangle.LayoutTransform>
        </Rectangle>
        <TextBlock Text="{Binding Value}" FontSize="8"/>
      </DockPanel>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Numeric labels can be added to the horizontal axis by using a second ItemsControl laid out horizontally, and with its data template a fixed width and showing numbers and tick marks.

Solution 3: Using low level Drawing classes

Build your graph by constructing a DrawingGroup object and adding GeometryDrawing and GlyphRunDrawing objects to it, then putting the DrawingGroup inside DrawingVisual and add that to your main Panel.

You should use one GeometryDrawing or GlyphRunDrawing for each set of items sharing a given brush and pen. For example if your axes and tick marks are all the same color and width, create a single GeometryDrawing for all of them, but if each tick mark is a differnet color, create multiple GeometryDrawing objects.

You will create a Geometry object for each GeometryDrawing. For the best efficiency it should be a StreamGeometry, but the other Geometry classes also work well, may be easier to use, and may be initialized in XAML. Creating a PathGeometry or EllipseGeometry is probably already familar to you so I'll focus on creating a StreamGeometry. You do this by calling the Open method in a using() statement, then writing to the returned context. Here is an example:

Geometry BuildAxesAndTicksGeometry()
{
  // First create geometry
  var geometry = new StreamGeometry();
  using(var context = geometry.Open())
  {
    // Horizontal axis
    context.BeginFigure(new Point(0,0), false, false);
    context.LineTo(new Point(_width, 0), true, false);

    // Vertical axis
    context.BeginFigure(new Point(0,0), false, false);
    context.LineTo(new Point(0, _height), true, false);

    // Horizontal ticks
    for(int i=0; i<_nTicksHorizontal; i++)
    {
      context.BeginFiture(new Point(i * _tickSpacing, -10), false, false);
      context.LineTo(new Point(i * _tickSpacing, 10), true, false);
    }
    // Do same for vertical ticks
  }

  // Now add it to a drawing
  return new GeometryDrawing { Geometry = geometry, Stroke = _axisPen };
}

Drawing BuildDrawing()
{
  var mainDrawing = new DrawingGroup();
  mainDrawing.Add(BuildAxesAndTicksGeometry());
  ... // Add other drawings, including one or more for the data
  return mainDrawing;
}

void UpdateDrawing()
{
  myDrawingVisual.Drawing = BuildDrawing();  // where myDrawingVisual is defined in the XAML
}

Comparison of solutions

For most cases I would recommend solution 2 or 3, for these reasons:

  • If the graph is simple enough to use data binding it will save you a lot of time. Go with solution 2.
  • If the graph cannot be done with data binding, using Drawing objects is approximately as simple as any other technique, and can perform better. Go with solution 3.

In your case if you've already invested significant work into your Solution 1, you may want to stick with it even though it probably isn't the best.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top