CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 9 of 9
  1. #1
    Join Date
    Jun 2011
    Location
    Buenos Aires, Argentina
    Posts
    130

    [RESOLVED] File in use by another process problem

    Hello again! I'm having a little problem with a .BackImage property file I want to modify at run time, but can't manage to "release" the file to be able to change what I need. This is what I want to do and what I've tried:

    I have a control (ChartArea) with a back image assigned. When values change I want to update the back image of the chart to show the new statistics. I have no problem creating the image file, but the updating only works the first time, when the file was still never assigned as a back image fot the control. After that, I get a "The process can't access the file <path> because it is being used by another process" message.

    I've tried the following:
    Code:
    // Some notes
    // ImageArray is a byte[]
    // string ChartBackGroundFile points to the path where the file should be written/overwritten (something like "C:\TestApp\TestFile.png")
    // WriteAllBytes creates if necessary and overwrites if existing.
    
    ImageArray = GetBackGroundImage(Text, TextColor);
    
    localChartArea.BackImage = "";
    Application.DoEvents();
    File.WriteAllBytes(ChartBackGroundFile, ImageArray);
    localChartArea.BackImage = ChartBackGroundFile;
    Since the .BackImage property only accepts a string pointing to a file, I have to actually write the file to hard drive. The problem is that assigning "" to it doesn't seem to release the file. The exception is thrown at WriteAllBytes().

    Any ideas?
    Thanks a lot,

  2. #2
    Join Date
    Oct 2004
    Location
    Rocket City
    Posts
    220

    Re: File in use by another process problem

    The exception is thrown at WriteAllBytes()
    Have you tried putting localChartArea.BackImage = null before the WriteAllBytes()?
    Code:
    localChartArea.BackImage = null;
    File.WriteAllBytes(ChartBackGroundFile, ImageArray);

  3. #3
    Join Date
    Jan 2010
    Posts
    1,133

    Re: File in use by another process problem

    System.Web.UI.DataVisualization.Charting.ChartArea or System.Windows.Forms.DataVisualization.Charting.ChartArea?
    Anyway, the following applies to both.

    I suppose that the BackImage property was only intended to get a static background from an URL, stored somewhere on a server (or locally).
    I think you can use the PrePaint and/or the PostPaint event of the Chart class:
    Quote Originally Posted by MSDN (for System.Web.UI.DataVisualization.Charting.Chart)
    SRC
    • PrePaint - Occurs after the chart element backround is drawn. This event is raised for elements such as ChartArea and Legend.
    • PostPaint - Occurs after chart element was drawn. This event is raised for elements such as ChartArea and Legend.
    Quote Originally Posted by MSDN (for System.Windows.Forms.DataVisualization.Charting.Chart)
    SRC
    • PrePaint - Occurs when the chart element background is painted.
    • PostPaint - Occurs when the chart element is painted.
    The handler signature is: void HandlerName(object sender, ChartPaintEventArgs e).
    Then you can use e.ChartGraphics to obtain the GDI+ Graphics object (via ChartGraphics.Graphics), and you can then draw directly, without the need for a background file.
    Also, e.ChartElement get's the object that ChartGraphics will paint on (ChartArea derives indirectly from ChartGraphics).
    ChartGraphics (web, win) itself contains some helper conversion functions.
    Last edited by TheGreatCthulhu; August 2nd, 2012 at 12:32 PM.

  4. #4
    Join Date
    Jun 2011
    Location
    Buenos Aires, Argentina
    Posts
    130

    Re: File in use by another process problem

    Hi zips, the null assignment throws a nullReferenceException, already tried. The value for "no image" is just an empty string.

    Thulhu, thank you for the info. The chart I'm using is from the System.Windows.Forms family.
    As for the backimage, the pre/postpaint events are generated for the Chart element, not the ChartArea. The Chart will of course have the ChartAreas list with all the ChartArea(s) in it, but I would not know which one (since I have more than one) will have to be updated. My particular case makes it even more complicated since the CharArea(s) that hold my data are defined as members of a class, and the UI adds the instances of every existing class's ChartArea to the Chart.ChartAreas list at runtime, so I don't have access to the data I want to set as the backimage from the pre/postpaint event. Could/Should I set the ChartArea.Tag property to a class object that holds that data, including a bool when new data is available so I don't update every ChartArea every time the Chart repaints, and read every Tag when the event runs?

    Regarding what you mention about how to draw the ChartArea directly, I don't have a clue on how to do that! =) Would I need to override the OnPaint? I'm forseeing mayor headaches with all the tick marks, labels, legends, lines and all that. Or can I just draw the background and let everything else be taken care of as it would normally? I'll start researching, but if you know of a good place to start, I'd be very welcome.

    Thanks for the help.

    Offtopic PS: Is the forums autologout timeout a bit short or is it just me?

  5. #5
    Join Date
    Jan 2010
    Posts
    1,133

    Re: File in use by another process problem

    So you're just updating the background image, and the ChartArea draws the data? I guess you would use the PrePaint event in that case; overriding OnPaint() or handling Paint event is not necessary, because, to my understanding, they are used for painting the whole Graph. Depending on what you're doing, it might be enough just to use the Graphics class to draw the background image (one method call). What kind of an update is it? How is the image generated? Does it involve some elaborate graphics, or it's just colored text?

    Generally, using the Tag property is discouraged (it's considered more of a hackish solution), but it could work.
    In the meantime, I'll try creating a test project myself, and I'll post here if I come up with something.

  6. #6
    Join Date
    Jun 2011
    Location
    Buenos Aires, Argentina
    Posts
    130

    Re: File in use by another process problem

    Yes, there's the <measuringPoint> class that has the ChartArea, Legend and Series, and then the GUI that has the Chart. Since (so far) all I did was update the .Points data in the Series, I had no need for the Chart to interact. It actually worked very nice since every class instance had control over it's graph, and the Chart would just repaint.
    Now the <measuringPoint> class would like to draw the ChartArea's background, and that's when this happened. I would definetly use PrePaint for this. PostPaint would imply a re-paint as far as I'm guessing. All I want is to set the BackImage of the ChartArea (the InnerPlotPosition actually, which is what the BackImage covers), but doing it directly would need an existing file, and using events would have to go through the Chart's code, instead of the class that uses the ChartArea.

    The update will occur after a new DataPoint is added to a Series. The BackImage is very simple, just a plain solid background and a string converted to image. Sort of a watermark for statistics about the graph.

    The methods I'm using are:
    Code:
    public void UpdateBackGround(string Text, Brush TextColor)
    {
    	byte[] ImageArray;
    
    	ImageArray = GetBackGroundImage(Text, TextColor);
    	localChartArea.BackImage = "";
    	Application.DoEvents();
    
    	try
    	{
    		File.WriteAllBytes(ChartBackGroundFile, ImageArray);
    		localChartArea.BackImage = ChartBackGroundFile;
    	}
    	catch
    	{
    		// Curses
    	}
    }
    And
    Code:
    private byte[] GetBackGroundImage(string Text, Brush TextColor)
    {
    	double x1 = localChartArea.AxisX.ValueToPixelPosition(localChartArea.AxisX.Minimum);
    	double x2 = localChartArea.AxisX.ValueToPixelPosition(localChartArea.AxisX.Maximum);
    	double y1 = localChartArea.AxisY.ValueToPixelPosition(localChartArea.AxisY.Maximum);
    	double y2 = localChartArea.AxisY.ValueToPixelPosition(localChartArea.AxisY.Minimum);
    
    	int textFontSize = 5;
    	Size PlotArea = new Size((int)(x2 - x1), (int)(y2 - y1));
    	Font textFont = new Font("Tahoma", textFontSize, FontStyle.Bold, GraphicsUnit.Pixel);
    	SizeF textSize = TextRenderer.MeasureText(Text, textFont);
    
    	do
    	{
    		// ninja style text width calculation
    		textFontSize += 5;
    		textFont = new Font("Tahoma", textFontSize, FontStyle.Bold, GraphicsUnit.Pixel);
    		textSize = TextRenderer.MeasureText(Text, textFont);
    	} while ((textSize.Width < 0.9 * PlotArea.Width) && (textSize.Height < 0.9 * PlotArea.Height));
    			
    	int bmpWidth = (int)(x2 - x1);
    	int bmpHeight = (int)(y2 - y1);
    
    	PointF textPoint = new PointF((bmpWidth - textSize.Width) / 2, (bmpHeight - textSize.Height) / 2);
    
    	using (Bitmap bmp = new System.Drawing.Bitmap(bmpWidth, bmpHeight))
    	{
    		using (Graphics g = Graphics.FromImage(bmp))
    		{
    			g.Clear(localChartArea.BackColor);
    			g.DrawString(Text, textFont, TextColor, textPoint);
    		}
    
    		MemoryStream mem = new MemoryStream();
    		bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Png);
    
    		return mem.ToArray();
    	}
    }
    As you can see, it's quite simple. The GetBackGroundImage should be useful as is to be used either to write to a file (as I'm doing now) or to draw the BackImage as you said, if I'm getting any of this.

  7. #7
    Join Date
    Jan 2010
    Posts
    1,133

    Re: File in use by another process problem

    Well, I managed to do some neat custom drawing

    Name:  ChartTest.jpg
Views: 1526
Size:  53.7 KB

    The painting of the chart control is done like this:


    (chart invalidated) --> the BackImage is drawn -->

    --> OnPrePaint() method is called, which also fires the PrePaint event
    (allowing for some other object to do the painting) -->

    --> OnPaint() method is called, which also fires the Paint event
    (allowing for some other object to do the painting) -->

    --> OnPostPaint() method is called, which also fires the PostPaint event
    (allowing for some other object to do the painting)
    PrePaint paints over the background color/image, but under the plot; Paint paints the data chart itself (i think), and PostPaint let's you draw on top of everything. All in one draw cycle.
    You would work with the graphics class the same way you used it your GetBackGroundImage() method.

    Both the ChartArea and the Series classes define the Name property, which you can use for identification. You could use a Dictionary<TKey, TValue> instance to map a ChartArea to a Series, or you could simply iterate through all the series until you find one with ChartArea property matching the name of the chart area you're looking for.

    You can obtain the ChartArea in the pre/post-paint handler like this:
    Code:
    if (e.ChartElement is ChartArea)
    {
        ChartArea area = (ChartArea)e.ChartElement;
    
        // do other stuff...
    }
    This is my entire PrePaint handler:
    Code:
            private void Chart_PrePaint(object sender, System.Windows.Forms.DataVisualization.Charting.ChartPaintEventArgs e)
            {
                if (e.ChartElement is ChartArea)
                {
                    ChartArea area = (ChartArea)e.ChartElement;
                    Graphics g = e.ChartGraphics.Graphics;
    
                    // These get the relative coordinates of the graph, whatever that means...
                    // Note: you could use area.InnerPlotPosition instead.
                    float x = area.Position.X;
                    float y = area.Position.Y;
                    float width = area.Position.Width;
                    float height = area.Position.Height;                
    
                    // The Graphics class uses absolute coordinates, so you need to use a
                    // ChartGraphics helper method to convert.
                    RectangleF rectangle = new RectangleF(x, y, width, height);
                    rectangle = e.ChartGraphics.GetAbsoluteRectangle(rectangle);
    
                    Bitmap backImage = SelectImage(area);
    
                    if (backImage != null)
                        g.DrawImage(backImage, rectangle);
                }
            }
    I attached the code below - I'm it's not very complicated, I'm sure you can adapt it or otherwise use it to solve your problem.
    Attached Files Attached Files

  8. #8
    Join Date
    Jun 2011
    Location
    Buenos Aires, Argentina
    Posts
    130

    Re: File in use by another process problem

    Ok, this is how my PrePaint event resulted. It will calculate the position of the InnerPlotPosition considering the ChartArea's position and the Chart size. I had to change to calculation of the RectangleF since using the AxisX.Minimum and Maximum values conflicted when I zoomed in on any of the graphs. InnerPlotPosition and Position are set in percentage coordinates of the containing object, so there is a small conversion inbetween.
    The drawn texts size calculation is working a bit odd, but that's another problem.
    Code:
    if (e.ChartElement is ChartArea)
    {
    	ChartArea localChartArea = (ChartArea)e.ChartElement;
    	RectangleF InnerPlot = localChartArea.InnerPlotPosition.ToRectangleF();
    
    	InnerPlot.X = (localChartArea.Position.X / 100) * e.Chart.Width + (InnerPlot.X / 100) * (localChartArea.Position.Width / 100) * e.Chart.Width;
    	InnerPlot.Y = (localChartArea.Position.Y / 100) * e.Chart.Height + (InnerPlot.Y / 100) * (localChartArea.Position.Height / 100) * e.Chart.Height;
    	InnerPlot.Width = (InnerPlot.Width / 100) * e.Chart.Width;
    	InnerPlot.Height = (InnerPlot.Height / 100) * (e.Chart.Height / e.Chart.ChartAreas.Count);
    
    	int textFontSize = 5;
    	Font textFont = new Font("Tahoma", textFontSize, FontStyle.Bold, GraphicsUnit.Pixel);
    	SizeF textSize = TextRenderer.MeasureText(Text, textFont);
    
    	do
    	{
    		textFontSize += 5;
    		textFont = new Font("Tahoma", textFontSize, FontStyle.Bold, GraphicsUnit.Pixel);
    		textSize = TextRenderer.MeasureText(Text, textFont);
    	} while ((textSize.Width < 0.3 * InnerPlot.Width) && (textSize.Height < 0.3 * InnerPlot.Height));
    
    	PointF textPoint = new PointF(2, (InnerPlot.Height - textSize.Height) / 1);
    
    	using (Bitmap bmp = new System.Drawing.Bitmap((int)InnerPlot.Width, (int)InnerPlot.Height))
    	{
    		using (Graphics g = Graphics.FromImage(bmp))
    		{
    			g.Clear(localChartArea.BackColor);
    			g.DrawString(DateTime.Now.ToShortTimeString() + " - " + localChartArea.Tag.ToString(), textFont, Brushes.Aquamarine, textPoint);
    		}
    
    		e.ChartGraphics.Graphics.DrawImage(bmp, InnerPlot);
    	}
    }
    For now I'm just storing the text to be written in the Tag property of the ChartArea. I'll see how to clean that up later.

    Thanks a lot for the help!

  9. #9
    Join Date
    Jan 2010
    Posts
    1,133

    Re: [RESOLVED] File in use by another process problem

    A few remarks:
    • Inner plot size calculation: Oh, I see - the relative coordinates are in the [0, 100] range. But, unless it's publicly documented somewhere, it's an internal implementation detail (which means it's potentially subject to change), so I'd prefer using Inner InnerPlot = e.ChartGraphics.GetAbsoluteRectangle(InnerPlot) instead of all the RectangleF conversion code. If I'm not mistaken, that should also take care of zooming as well.
    • The bitmap: Since you're only drawing a string, you don't really need a separate "back buffer" Bitmap object, but it's not wrong to use it. However, if the graph is being resized, and so you need to recalculate the size of the inner plot area / bitmap on each update, you can avoid creating a new bitmap object by simply not using one, and drawing directly to e.ChartGraphics.Graphics. If the bitmap size didn't change, you could (and should) promote the local bitmap variable to a member filed, construct it only once, create the bitmap's graphics only once, and reuse them on each update.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured