Ошибка: Invoke или BeginInvoke не могут быть вызваны для элемента управления, пока не будет создан дескриптор окна - PullRequest
1 голос
/ 09 февраля 2012

Во-первых, позвольте мне сказать, что я уже почти 2 дня занимаюсь этим вопросом: пробовал различные варианты и читал множество вопросов с вашего сайта с похожими проблемами.

Сначала позвольте мне вставить мой код, чтобы вы получили полную картину.

РЕДАКТИРОВАТЬ: Удален код, который может быть ненужным для проблемы, на основе запроса от участника форума.

Форма 1.cs

namespace TestApp
{

public partial class Form1 : Form    
{
    //global declarations
    private static Form1 myForm;
    public static Form1 MyForm
    {
        get
        {
            return myForm;
        }
    }       
    List<DataSet> DataSets = new List<DataSet>();
    int typeOfDataset = 0;
    //delegate for background thread to communicate with the UI thread _
    //and update the metadata autodetection progress bar
    public delegate void UpdateProgressBar(int updateProgress);
    public UpdateProgressBar myDelegate;


    //***************************
    //****  Form Events  ********
    //***************************

    public Form1()
    {
       InitializeComponent();
       if (myForm == null)
       {
           myForm = this;
       }
       DataSets.Add(new DSVDataSet());
       DataSets[typeOfDataset].BWorker = new BackgroundWorker();
       DataSets[typeOfDataset].BWorker.WorkerSupportsCancellation = true;
       myDelegate = new UpdateProgressBar(UpdateProgressBarMethod);
     }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        //e.Cancel = true;
        if (DataSets[typeOfDataset].BWorker != null || DataSets[typeOfDataset].BWorker.IsBusy)
        {
            Thread.Sleep(1000);
            DataSets[typeOfDataset].BWorker.CancelAsync();
        }
        Application.Exit();
    }

    //***************************
    //***  Menu Items Events  ***
    //***************************

    private void cSVToolStripMenuItem_Click(object sender, EventArgs e)
    {
        LoadFillDSVData(',');
    }

    //***************************
    //*** DataGridViews Events **
    //***************************

    private void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
    {
        using (SolidBrush b = new SolidBrush(this.dataGridView1.RowHeadersDefaultCellStyle.ForeColor))
        {
            e.Graphics.DrawString(e.RowIndex.ToString(System.Globalization.CultureInfo.CurrentUICulture), this.dataGridView1.DefaultCellStyle.Font, b, e.RowBounds.Location.X + 20, e.RowBounds.Location.Y + 4);
        }
        int rowHeaderWidth = TextRenderer.MeasureText(e.RowIndex.ToString(), dataGridView1.Font).Width;
        if (rowHeaderWidth + 22 > dataGridView1.RowHeadersWidth)
        {
            dataGridView1.RowHeadersWidth = rowHeaderWidth + 22;
        }
    }

    private void dataGridView2_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
    {
        using (SolidBrush b = new SolidBrush(this.dataGridView2.RowHeadersDefaultCellStyle.ForeColor))
        {
            e.Graphics.DrawString(e.RowIndex.ToString(System.Globalization.CultureInfo.CurrentUICulture), this.dataGridView2.DefaultCellStyle.Font, b, e.RowBounds.Location.X + 20, e.RowBounds.Location.Y + 4);
        }
        int rowHeaderWidth = TextRenderer.MeasureText(e.RowIndex.ToString(), dataGridView2.Font).Width;
        if (rowHeaderWidth + 22 > dataGridView2.RowHeadersWidth)
        {
            dataGridView2.RowHeadersWidth = rowHeaderWidth + 22;
        }
    }

    //***************************
    //****** Other Methods ******
    //***************************

    private void LoadFillDSVData(char delimiter)
    {
        //load file through openFileDialog
        //some more openFileDialog code removed for simplicity
        if (DataSets[typeOfDataset].BWorker != null || DataSets[typeOfDataset].BWorker.IsBusy)
        {
            Thread.Sleep(1000);
            DataSets[typeOfDataset].BWorker.CancelAsync();
            DataSets[typeOfDataset].BWorker.Dispose();
        }
        //if file was loaded, instantiate the class
        //and populate it 
        dataGridView1.Rows.Clear();
        dataGridView2.Rows.Clear();
        typeOfDataset = 0;
        DataSets[typeOfDataset] = new DSVDataSet();
        DataSets[typeOfDataset].DataGrid = this.dataGridView1;
        DataSets[typeOfDataset].BWorker = new BackgroundWorker();
        DataSets[typeOfDataset].InputFile = openFileDialog1.FileName;
        DataSets[typeOfDataset].FileName = Path.GetFileName(DataSets[typeOfDataset].InputFile);
        DataSets[typeOfDataset].InputPath = Path.GetDirectoryName(DataSets[typeOfDataset].InputFile);
        DataSets[typeOfDataset].Delimiter = delimiter;
        //read file to get number of objects and attributes
        DataSets[typeOfDataset].LoadFile(DataSets[typeOfDataset].InputFile, DataSets[typeOfDataset].Delimiter);
        //ask to autodetect metadata
        DialogResult dr = MessageBox.Show("Auto detect attributes?", "TestApp", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
        switch (dr)
        {
            case DialogResult.Yes:
                toolStripStatusLabel1.Text = "Autodetecting attributes...";
                toolStripProgressBar1.Value = 0;
                toolStripProgressBar1.Maximum = 100;
                toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
                DataSets[typeOfDataset].AutoDetectMetadata(DataSets[typeOfDataset].Attributes);
                break;
            case DialogResult.No: 
                break;
            default: 
                break;
        }
    }

    public void UpdateProgressBarMethod(int progress)
    {
        if (progress > 99)
        {
            toolStripProgressBar1.Value = progress;
            toolStripStatusLabel1.Text = "Done.";
        }
        else
        {
            toolStripProgressBar1.Value = progress;
        }
    } 

}

}

И еще один класс:

DSVDataSet.cs

namespace TestApp
{

public class DSVDataSet : DataSet
{
    static Form1 myForm = Form1.MyForm;
    //constructor(s)
    public DSVDataSet()
    {           
        InputType = DataSetType.DSV;
    }


    //autodetects metadata from the file if
    //the user wishes to do so
    public override void AutoDetectMetadata(List<Attribute> attributeList)
    {
        BWorker.WorkerReportsProgress = true;
        BWorker.WorkerSupportsCancellation = true;
        BWorker.DoWork += worker_DoWork;
        BWorker.ProgressChanged += worker_ProgressChanged;
        BWorker.RunWorkerCompleted += worker_RunWorkerCompleted;

        //send this to another thread as it is computationally intensive
        BWorker.RunWorkerAsync(attributeList);
    }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        List<Attribute> attributeList = (List<Attribute>)e.Argument;
        using (StreamReader sr = new StreamReader(InputFile))
        {
            for (int i = 0; i < NumberOfAttributes; i++)
            {
                Attribute a = new Attribute();
                attributeList.Add(a);
            }
            for (int i = 0; i < NumberOfObjects; i++)
            {
                string[] DSVLine = sr.ReadLine().Split(Delimiter);
                int hoistedCount = DSVLine.Count();
                string str = string.Empty;
                for (int j = 0; j < hoistedCount; j++)
                {
                    bool newValue = true;
                    str = DSVLine[j];
                    for (int k = 0; k < attributeList[j].Categories.Count; k++)
                    {
                        if (str == attributeList[j].Categories[k])
                        {
                            newValue = false;
                        }
                    }
                    if (newValue == true)
                    {
                        attributeList[j].Categories.Add(str);
                        //removed some code for simplicity
                    }
                }
                int currentProgress = (int)((i * 100) / NumberOfObjects);
                if (BWorker.CancellationPending)
                {
                    Thread.Sleep(1000);
                    e.Cancel = true;
                    return;
                }
                BWorker.ReportProgress(currentProgress); //report progress
            }
        }          
        e.Result = 100; //final result (100%)     
    }

    private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        int update = e.ProgressPercentage;
        myForm.BeginInvoke(myForm.myDelegate, update);
    }

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {               

        if (e.Error != null)
        {
            return;
        }
        if (e.Cancelled)
        {
            return;
        }
        else
        {
            int update = (int)e.Result;
            myForm.Invoke(myForm.myDelegate, update);
            for (int i = 0; i < Attributes.Count; i++)
            {
                DataGridViewRow item = new DataGridViewRow();
                item.CreateCells(DataGrid);
                item.Cells[0].Value = Attributes[i].Name;
                switch (Attributes[i].Type)
                {
                    case AttributeType.Categorical:
                        item.Cells[1].Value = "Categorical";
                        break;
                    //removed some cases for simplicity
                    default:
                        item.Cells[1].Value = "Categorical";
                        break;
                }
                item.Cells[2].Value = Attributes[i].Convert;
                DataGrid.Rows.Add(item);
            }
            BWorker.Dispose();
        }
    }
}

}

Короче говоря, у меня есть файл backGroundWorker в DSVDataset.cs, который выполняет некоторые «тяжелые вычисления», чтобы пользовательский интерфейс не зависал (новичок в этом), и я использую делегат для создания фонового потока для связи с пользовательским интерфейсом. поток, чтобы обновить некоторые значения индикатора выполнения. Если пользователь решает создать новый DSVDataSet (через cSVToolStripMenuItem_Click или tSVToolStripMenuItem_Click), тогда я добавлю несколько ifs везде, где я сочту целесообразным, проверить, есть ли уже запущенный backGroundWorker: если есть, вызовите CancelAsync, чтобы он прекратил все, что делает, и тогда. Распоряжайся. Теперь, иногда, когда я пытаюсь выйти из Form1 во время работы backGroundWorker (проверьте, например, Form1_FormClosing), я получаю сообщение об ошибке в заголовке этого поста. Ошибка приводит меня к этому фрагменту кода в DSVDataSet.cs:

    private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        int update = e.ProgressPercentage;
        myForm.BeginInvoke(myForm.myDelegate, update);
    }

Нажатие F10 переводит меня в Program1.cs:

   Application.Run(new Form1());

Основываясь на другой статье, которую я прочитал (/509425/oshibka-kompilyatsii-invoke-begininvoke-mogut-vyzvany-dlya-elementa-upravleniya-poka-budet-sozdan-deskriptor-okna, посмотрите на ответ), я реализовал эту логику, выставив экземпляр самого класса Main, чтобы он всегда указывал на этот экземпляр main. Так в Form1.cs:

private static Form1 myForm;
    public static Form1 MyForm
    {
        get
        {
            return myForm;
        }
    }       
 public Form1()
    {
       InitializeComponent();
       if (myForm == null)
       {
           myForm = this;
       }
     }

А в DSVDataset.cs:

  static Form1 myForm = Form1.MyForm;

и я использую myForm везде, где это необходимо.

Тем не менее, я не могу обойти эту ошибку, поэтому могу только предположить, что я не реализовал это решение правильно, или я делаю что-то не так с обработкой backGroundWorkers. Любая помощь будет принята с благодарностью, и я приложил все усилия, чтобы быть максимально подробным (надеюсь, это не было излишним:)

Привет

Ответы [ 3 ]

3 голосов
/ 09 февраля 2012

Как говорит dBear, вполне вероятно, что ваш фоновый работник все еще запускает события, изменяющие прогресс, после удаления вашей формы. Было бы неплохо либо заблокировать закрытие формы до тех пор, пока не завершится работа фонового работника, либо убить фонового работника перед закрытием формы. Независимо от того, какую из этих опций вы выберете, лучше осуществлять такую ​​связь, используя события. Добавьте следующее определение события в DSVDataSet и измените обработчик события изменения хода выполнения:

public event ProgressChangedEventHandler ProgressChanged;

private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    if (ProgressChanged != null) {
        ProgressChanged(this, e);
    }
}

Как только вы это сделаете, вам нужно будет внести изменения в Form1, чтобы после того, как вы создали новый экземпляр DSVDataSet, вы добавили обработчик события:

dsv.ProgressChanged += new ProgressChangedEventHandler(dsv_ProgressChanged);

Введите любой код, необходимый для отображения прогресса в теле dsv_ProgressChanged, например:

void dsv_ProgressChanged(object sender, ProgressChangedEventArgs e) {
     myForm.Invoke(myForm.myDelegate, update);
}
3 голосов
/ 09 февраля 2012

Код слишком много, чтобы понять их все. Но кажется, что фоновый рабочий все еще работает, когда вы закрываете форму, поэтому изменение прогресса фонового рабочего приведет к сбою вашей программы.

Для быстрого решения, проверьте состояние формы при изменении прогресса, так как у меня нет большого опыта работы с winform, но должен существовать метод, чтобы проверить, закрывается или удаляется форма.

Это не рекомендуется, так как соединит пользовательский интерфейс и логику.

2 голосов
/ 09 февраля 2012

Во-первых, кажется, что вы пытаетесь создать одноэлементный экземпляр Form1.Лучший способ сделать это - создать приватный конструктор со статическим методом, свойством или переменной, который создает новый экземпляр формы:

public class Form1 : Form
{
    private static Form1 instance;
    public static Form1 Instance
    {
        get 
        { 
           if(instance == null) { instance = new Form1(); }
           return instance;
        }
    }

    private Form1()
    {
        ......
    }
}

Затем измените:

    Application.Run(new Form1());

Кому:

    Application.Run(Form1.Instance);

Затем вы можете использовать Form1.Instance в любом другом классе для доступа к главному окну.Теперь ошибка дескриптора окна обычно связана с тем, что форма (Form1) не создается в главном потоке.

Следование приведенному мною шаблону может решить вашу проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...