This project has moved. For the latest updates, please go here.

Flickering with ListView in Editor GUI

Topics: Defect?, Editor UI
Jan 16, 2013 at 9:59 AM

Hi again Marc!

I want to add some visual feedback to my midi mapper VST, and the mappings are displayed in a ListView.  However, I'm getting terrible flicker every time I update the ListView.

After a couple of hours Googling, it seems the standard way of solving this in a Visual C# project is to subclass the ListView and setting the DoubleBuffered property, like so:

class BufferedListView : ListView
{
       public BufferedListView()
        {
            this.DoubleBuffered = true;
            this.UpdateStyles();
        }
}

When I do this on a standalone C# project it works perfectly.  But the same code placed inside a VST.NET project still results in terrible flickering when ever the list items are updated, just like the original class does.

Could there be something in the WinFormsControlWrapper etc. that is interfering with the double buffering window properties?

Thanks, Leif

Jan 16, 2013 at 2:19 PM

The 'DoubleBuffered' property is more of a suggestion to the control than a way to force double buffering in all case.

A manual double buffering in your sub-classed control should yield better results.

Source: http://www.bobpowell.net/doublebuffer.htm

Example:

    private Bitmap _backBuffer;
   
    protected override void OnPaint(PaintEventArgs e)
    {
      if(_backBuffer==null)
      {
        _backBuffer=new Bitmap(this.ClientSize.Width,this.ClientSize.Height);
      }
      Graphics g=Graphics.FromImage(_backBuffer);
     
      //Paint your graphics on g here
     
      g.Dispose();
 
      //Copy the back buffer to the screen
 
      e.Graphics.DrawImageUnscaled(_backBuffer,0,0);
 
      //base.OnPaint (e); //optional but not recommended
    }
 
    protected override void OnPaintBackground(PaintEventArgs pevent)
    {
      //Don't allow the background to paint
    }
 
    protected override void OnSizeChanged(EventArgs e)
    {
      if(_backBuffer!=null)
      {
        _backBuffer.Dispose();
        _backBuffer=null;
      }
      base.OnSizeChanged (e);
    }

Jan 16, 2013 at 2:23 PM

Just a note, when automatic double-buffering with the DoubleBuffered property doesn't work it's often because the window/control has a background image, is using transparency, or a semi-transparent background color.

Coordinator
Jan 16, 2013 at 4:55 PM

The WinFormsControlWrapper is dead easy. Basically it's only there to attach the Win32 hWnd as the parent of the control.

First of all make absolutely sure you're not calling (related) methods multiple times (not something like Clear list and then add all items again) or set the selected item after each add or try to keep the last added item in view. If you have anything of that sort of code, comment it out for now. Bring your code back to the bear minimum.

What type of information does your ListView show?

What I'm getting at is that a control is only supposed to be called by the Thread that created it. If you update incoming midi note or related information in that ListView, there is a big change you use the Thread of the Host to make the call to the ListView. I know that in WPF you get an exception when you do that and I've heard that WinForms isn't that strict.

So use the (WinForms) Control.Invoke (and InvokeRequired) method(s) to post any calls to the UI from within the plugin.

Hope it helps.

Jan 16, 2013 at 5:40 PM

@YuryK: There is a background image, but removing it didn't help.  Plus even with a background image on the standalone version of my app, I don't get any flicker at all with DoubleBuffered set.  Not using Transparency anywhere, as far as I can tell.

I'll give your example a try, but I'm still fairly new to C# and don't know what I would put in the section "//Paint your graphics on g here"  ;-)

Jan 16, 2013 at 5:49 PM
Edited Jan 16, 2013 at 5:54 PM

@obiwanjacobi:  All I'm trying to set is the .BackColor property on my ListView items to indicate when a match is received on incoming MIDI events (although the same issue comes up if I change text etc.).

At first I was doing this inside ProcessIdle(), but then moved it to a Timer instantiated in the Form's constructor, so that should be the same thread?  It didn't help, anyway.  (But the same code works fine in a non-VST.NET project).

As for what information it shows, it's based on your MidiMapper example so it should look somewhat familiar ;-)    (This is the derived app I asked you about a while back).

Screenshots and the source are here.

https://github.com/LeifBloomquist/MIDIMapperX

https://github.com/LeifBloomquist/MIDIMapperX/tree/master/Source/SchemaFactor.Vst.MidiMapperX

This particular commit shows all the wacky ways I was trying to get the flickering to go away, with no luck.

The plugin itself works beautifully, it's just this flicker!

Coordinator
Jan 16, 2013 at 5:55 PM

Have you tried a different host? FL is notorious... 

Jan 16, 2013 at 6:09 PM

<Head explodes>  You're right - In another host, or if I switch the plugin to "Bridged Mode" in FL, the flicker goes away.  I've commented a lot of code out, let me re-enable it to be sure.

There must be a way for me to force the window to draw with buffering though, maybe using the manual method YuryK suggested will help.  But at least I have a likely workaround, thanks!

Jan 16, 2013 at 6:43 PM

Sorry I meant double buffing the parent container of the listview in addition of double buffering your list view.

I'm not sure if yoour parent container is a UserControl or a Form.

Here is the full example for a manually double buffered Form containing an automatic double buffered list view.

I made it with from a new WindowsForm application wizard, to test just replace the "Program.cs" file created by the wizard.

If you need help for applying it to a UserControl just ask.

using System;
using System.Windows.Forms;
using System.Drawing;

namespace WindowsFormsApplication1
{
    class BufferedListView : ListView
    {
        public BufferedListView()
        {
            this.DoubleBuffered = true;
            this.UpdateStyles();
        }
    }

    public partial class DoubleBufferedForm : Form
    {
        public DoubleBufferedForm()
        {
            Controls.Add(new BufferedListView() { Width = ClientSize.Width, Height = ClientSize.Height, BackColor = Color.Red });
        }

        private Bitmap _backBuffer;

        protected override void OnPaint(PaintEventArgs e)
        {
            if (_backBuffer == null)
            {
                _backBuffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
            }

            Graphics g = Graphics.FromImage(_backBuffer);

            //Paint your graphics on g here

            g.Dispose();

            //Copy the back buffer to the screen

            e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0);

            //base.OnPaint (e); //optional but not recommended
        }

        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            //Don't allow the background to paint
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            if (_backBuffer != null)
            {
                _backBuffer.Dispose();
                _backBuffer = null;
            }
            base.OnSizeChanged(e);
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new DoubleBufferedForm());
        }
    }
}

Jan 31, 2013 at 10:39 PM
Hi YuryK,

Thanks for your reply! The parent container is in fact a UserControl. I tried changing "DoubleBufferedForm : Form" to " DoubleBufferedUserControl : UserControl" and it compiles and runs OK, but I am still getting terrible flickering. Is there another change needed?

The source is here, if you're feeling brave:

https://github.com/LeifBloomquist/MIDIMapperX

The flickering ListView in question is MapListVw in MidiNoteMapperUI. MapListVw is a BufferedListView, and MidiNoteMapperUI is a DoubleBufferedUserControl as per your example above. I believe I changed the relevant designer code to make sure the derived classes are being used instead. Still no luck.

But if you could have a look at the code or have any other suggestions, I'd be very grateful.
Feb 2, 2013 at 8:31 PM
The code source and project was easy to get up and running but I can't load the plugin in FLStudio :
Image

I kind of loathe FruityLoops... I just dumped all the files in their folder :
C:\Program Files (x86)\Image-Line\FL Studio 10\Plugins\VST\Jacobi.Vst.Core.dll
C:\Program Files (x86)\Image-Line\FL Studio 10\Plugins\VST\Jacobi.Vst.Framework.dll
C:\Program Files (x86)\Image-Line\FL Studio 10\Plugins\VST\SchemaFactor.Vst.MidiMapperX.dll

It scans all right but the plugin doesn't load, I'm using Windows 7 x64, FLStudio and plugin are 32 bit.
I'm not familiar with making plugins with VST.Net (only hosts), is there any additional step needed like registering Vst.Net in GacUtil needed ?
Feb 2, 2013 at 8:47 PM
OK Nevermind I managed to get it running by salvaging some bootstrapper files from the Binairies directory. I'll see if I can fix the flicker.
Feb 2, 2013 at 9:04 PM
Edited Feb 2, 2013 at 9:10 PM
I've been unable to reproduce the Flicker Bug in FLStudio. Are there any specific steps needed to reproduce this behavior?
I've noticed your controls do not fill the entire UserControl area, so I'd suggest drawing the background in DoubleBufferedUserControl, maybe it will help :
        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            //Don't allow the background to paint
            pevent.Graphics.FillRegion(Brushes.Black, new System.Drawing.Region(new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height)));
        }
I also suggest setting AllPaintingInWmPaint in the BufferedListView class :
        public BufferedListView()
        {
            this.DoubleBuffered = true;
            SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
            this.UpdateStyles();
        } 
Feb 2, 2013 at 10:17 PM
Also, when you need to update many items in a list control, make sure you call BeginUpdate and EndUpdate :
        private void TimerEventProcessor(Object myObject, EventArgs myEventArgs)
        {
            MapListVw.BeginUpdate();

            try
            {
                foreach (ListViewItem item in MapListVw.Items)
                {
                    byte noteNo = Byte.Parse(item.Name);

                    int green = (int)(_plugin.NoteMap[noteNo].TriggerPulseOn  * 255);
                    int red   = (int)(_plugin.NoteMap[noteNo].TriggerPulseOff * 255);

                    item.SubItems[0].BackColor = Color.FromArgb(red, green, 0);
                    _plugin.NoteMap[noteNo].Pulse();
                }
            }
            finally
            {
                MapListVw.EndUpdate();
            }
        }
Feb 3, 2013 at 10:33 PM
Edited Feb 3, 2013 at 10:34 PM
The DLLs should go under C:\Program Files (x86)\vstPlugins\ , but as long as you got it to work that's fine. I have a post-build step in the project to copy and rename the resulting DLL to that folder. (I was using a subfolder MidiMapperX)

To reproduce the flicker, make sure Bridged Mode is OFF under the Wrapper settings (little gear icon). Then enter a few dummy mappings and trigger them from the Piano Roll - I can send an .flp if needed.

I tried BeginUpdate and EndUpdate and it made the flicker dramatically worse. I think that's more intended for when changing text, all I'm doing is changing the item backgrounds.