C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

エクセルのセルの画像化時のエラーについて

[トピック内 7 記事 (1 - 7 表示)]  << 0 >>

■102826 / inTopicNo.1)  エクセルのセルの画像化時のエラーについて
  
□投稿者/ Massa (1回)-(2024/01/12(Fri) 15:21:06)

分類:[C#] 

2024/01/12(Fri) 15:43:03 編集(投稿者)
2024/01/12(Fri) 15:33:47 編集(投稿者)

<pre><pre>開発環境は
VS2022 C#
Framework 4.7.2
WinForms
Windows11

指定したセル(P1,P2)がからでない限り
Excelのセル範囲を一定間隔でずらしながら,画像として出力するプログラムをC#で作成しています.

以下のようなプログラムを作成したのですが,
range.CopPictureのところでエラーになるときとならないときがあり,不安定なってしまいます.
エラーメッセージは「range クラスの copypicture メソッドが失敗しました。」と表示されます.
解決法についてご教示いただければ幸いです.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Imaging;
using Microsoft.Office.Interop.Excel;
using System.IO;

namespace Excel2Image
{

public partial class Form1 : Form
{

string excelPath;

int _sheetNum = 6;

int _A1 = 9;
int _A2 = 4;
int _A3 = 23;
int _A4 = 29;

//int _photoNum;
int _P1 = 9;
int _P2 = 8;


int i = 1;
int pageNum = 1;

public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog openFile = new OpenFileDialog();

openFile.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

openFile.Filter = "Excelファイル(*.xls;*.xlsx)|*.xls;*.xlsx";
openFile.FilterIndex = 1;
DialogResult result = openFile.ShowDialog();

if (result == DialogResult.OK)
{
excelPath = openFile.FileName;

Microsoft.Office.Interop.Excel.Application ExcelApp = new Microsoft.Office.Interop.Excel.Application();
ExcelApp.Visible = false;

Microsoft.Office.Interop.Excel.Workbook ExcelWorkbook = ExcelApp.Workbooks.Open(excelPath);

Worksheet sheet = ExcelWorkbook.Sheets[_sheetNum];
sheet.Select();

while (sheet.Cells[_P1, _P2].Text != "")
{

Range range = sheet.Range[sheet.Cells[_A1, _A2], sheet.Cells[_A3, _A4]];

range.CopyPicture(XlPictureAppearance.xlScreen, XlCopyPictureFormat.xlBitmap);


IDataObject data = Clipboard.GetDataObject();




if (data.GetDataPresent(DataFormats.Bitmap))
{

Bitmap bmp = (Bitmap)data.GetData(DataFormats.Bitmap);

bmp.Save(i.ToString() + ".jpg", ImageFormat.Jpeg);
            
            i++;

}




if ((flg1 == false) && (flg2 == false))
{
flg1 = true;
_A1 = 9 + 38 * (pageNum - 1);
_A2 = 30;
_A3 = 23 + 38 * (pageNum - 1);
_A4 = 55;


}
else if ((flg1 == true) && (flg2 == false))
{
flg2 = true;
_A1 = 24 + 38 * (pageNum - 1);
_A2 = 4;
_A3 = 38 + 38 * (pageNum - 1);
_A4 = 29;


}
else if ((flg1 == true) && (flg2 == true))
{
flg1 = false;
_A1 = 24 + 38 * (pageNum - 1);
_A2 = 30;
_A3 = 38 + 38 * (pageNum - 1);
_A4 = 55;

}
else if ((flg1 == false) && (flg2 == true))
{
pageNum++;
flg2 = false;

_A1 = 9 + 38 * (pageNum - 1);
_A2 = 4;
_A3 = 23 + 38 * (pageNum - 1);
_A4 = 29;

}

_P1 = _A1;
_P2 = _A2 + 4;

}
ExcelApp.Quit();
}
}</pre></pre>
引用返信 編集キー/
■102827 / inTopicNo.2)  Re[1]: エクセルのセルの画像化時のエラーについて
□投稿者/ とっちゃん (809回)-(2024/01/12(Fri) 17:17:19)
No102826 (Massa さん) に返信
> 以下のようなプログラムを作成したのですが,
> range.CopPictureのところでエラーになるときとならないときがあり,不安定なってしまいます.
> エラーメッセージは「range クラスの copypicture メソッドが失敗しました。」と表示されます.
> 解決法についてご教示いただければ幸いです.

エラーになるときとならないときの違いはありますか?

例えば、アプリを終了せずに3回やるとエラーになるとか…
OS起動後初めて実行するときは問題がないとか…
あるいは、一定回数出力されると…とか。


デバッグはしてみたでしょうか?
もし、まだデバッグしていないのなら、デバッグ実行して、エラーになるタイミングで
_P1, _P2, _A1, _A2, _A3, _A4 のそれぞれのセル番号がはみ出していないかなども
確認してみるとよいかもしれません。


あとは、オートメーションオブジェクト(ExcelWorkbook, sheet, sheet.Cells[_P1, _P2] の戻り値ほか)が
解放されていないという点も注意が必要そうです。

エラーとは別にExcelが終了しないとかそういう状態になっていそう。

引用返信 編集キー/
■102828 / inTopicNo.3)  Re[1]: エクセルのセルの画像化時のエラーについて
□投稿者/ 魔界の仮面弁士 (3745回)-(2024/01/12(Fri) 17:36:54)
No102826 (Massa さん) に返信
> Excelのセル範囲を一定間隔でずらしながら,画像として出力するプログラムをC#で作成しています.
ReleaseComObject 関連の解放処理がゴッソリ抜け落ちてますね…。

C# からアウトプロセスで呼び出しているものなので、解放漏れがあると
Quit() 後も Excel のインスタンスが非表示で残り続ける要因に繋がりますよ。


> range.CopPictureのところでエラーになるときとならないときがあり,不安定なってしまいます.
スペルミスに目をつぶるとしても、提示されたコードは
 「{」が 11 個、
 「}」が 9 個
という非対称なものであり、そもそもコンパイルが通りそうにありません…。


> エラーメッセージは「range クラスの copypicture メソッドが失敗しました。」と表示されます.
クリップボードが使用中などで処理できなかった場合に、そのようなエラーが発生することがあります。
事象としては、Excel VBA だけで実施した場合にも起きえます。

他のアプリがクリップボード操作している場合や、PC のパフォーマンスが落ちている場合、
あるいは、クリップボードを連続して操作した場合などに起きやすいという説もありますが、
根本的な対策は無いので、遅延処理やリトライなどを独自に組み込んで対処している方が多いようです。

https://answers.microsoft.com/ja-jp/msoffice/forum/all/%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%A8%E3%83%A9/63090c23-3e6c-4855-80c5-d9e8684972c8
https://social.msdn.microsoft.com/Forums/ja-JP/35487cfb-26a1-465a-bc5e-a331d98f7a4e/123041237225945310341236712384123731235612305excel2016?forum=vbajp
https://vba-create.jp/vba-method-copypicture/


> 解決法についてご教示いただければ幸いです.
失敗した場合は、いったん現状の Click イベントを return で一時的に抜けて、
残りの処理を、別イベント (たとえば Application.Idle 時など)で実施するようリトライ処理を組み込んでみるとか。
引用返信 編集キー/
■102829 / inTopicNo.4)  Re[2]: エクセルのセルの画像化時のエラーについて
□投稿者/ Massa (3回)-(2024/01/12(Fri) 23:15:15)
2024/01/12(Fri) 23:16:19 編集(投稿者)
2024/01/12(Fri) 23:16:11 編集(投稿者)

ありがとうございます.

同じエクセルを開いても,3つのイメージが出力される時もあれば,
10個程度出力されるときもあり,
結果が異なります.

そのため,何が原因であるかよくわからない状態です.


> ■No102826 (Massa さん) に返信
>>以下のようなプログラムを作成したのですが,
>>range.CopPictureのところでエラーになるときとならないときがあり,不安定なってしまいます.
>>エラーメッセージは「range クラスの copypicture メソッドが失敗しました。」と表示されます.
>>解決法についてご教示いただければ幸いです.
>
> エラーになるときとならないときの違いはありますか?
>
> 例えば、アプリを終了せずに3回やるとエラーになるとか…
> OS起動後初めて実行するときは問題がないとか…
> あるいは、一定回数出力されると…とか。
>
>
> デバッグはしてみたでしょうか?
> もし、まだデバッグしていないのなら、デバッグ実行して、エラーになるタイミングで
> _P1, _P2, _A1, _A2, _A3, _A4 のそれぞれのセル番号がはみ出していないかなども
> 確認してみるとよいかもしれません。
>
>
> あとは、オートメーションオブジェクト(ExcelWorkbook, sheet, sheet.Cells[_P1, _P2] の戻り値ほか)が
> 解放されていないという点も注意が必要そうです。
>
> エラーとは別にExcelが終了しないとかそういう状態になっていそう。
>
引用返信 編集キー/
■102830 / inTopicNo.5)  Re[2]: エクセルのセルの画像化時のエラーについて
□投稿者/ Massa (4回)-(2024/01/12(Fri) 23:26:23)
ありがとうございます.

プログラムから一部をコピーした際に,
"}"が抜けておりました.
申し訳ございません.

ReleaseComObjectについては理解ができていませんでした.
また,C#での遅延処理は,Sleepなどでしょうか.

以下のようにプログラムを変更して,
エラーが出るCopyPictureの周りでSleepを入れたのですが,
状況の改善は見られませんでした.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Imaging;
using Microsoft.Office.Interop.Excel;
using System.IO;
using System.Threading;
using System.Runtime.InteropServices;

namespace Excel2Image
{

public partial class Form1 : Form
{

string excelPath;

int _sheetNum = 6;

int _A1 = 9;
int _A2 = 4;
int _A3 = 23;
int _A4 = 29;

//int _photoNum;
int _P1 = 9;
int _P2 = 8;


int i = 1;
int pageNum = 1;

bool flg1 = false;
bool flg2 = false;

public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog openFile = new OpenFileDialog();

openFile.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

openFile.Filter = "Excelファイル(*.xls;*.xlsx)|*.xls;*.xlsx";
openFile.FilterIndex = 1;
DialogResult result = openFile.ShowDialog();

if (result == DialogResult.OK)
{
excelPath = openFile.FileName;

Microsoft.Office.Interop.Excel.Application ExcelApp = new Microsoft.Office.Interop.Excel.Application();
ExcelApp.Visible = false;

Microsoft.Office.Interop.Excel.Workbook ExcelWorkbook = ExcelApp.Workbooks.Open(excelPath);

Worksheet sheet = ExcelWorkbook.Sheets[_sheetNum];
sheet.Select();

while (sheet.Cells[_P1, _P2].Text != "")
{

Range range = sheet.Range[sheet.Cells[_A1, _A2], sheet.Cells[_A3, _A4]];

Thread.Sleep(1000);
range.CopyPicture(XlPictureAppearance.xlScreen, XlCopyPictureFormat.xlBitmap);
Thread.Sleep(1000);

IDataObject data = Clipboard.GetDataObject();




if (data.GetDataPresent(DataFormats.Bitmap))
{

Bitmap bmp = (Bitmap)data.GetData(DataFormats.Bitmap);

bmp.Save(i.ToString() + ".jpg", ImageFormat.Jpeg);

i++;

}

if ((flg1 == false) && (flg2 == false))
{
flg1 = true;
_A1 = 9 + 38 * (pageNum - 1);
_A2 = 30;
_A3 = 23 + 38 * (pageNum - 1);
_A4 = 55;


}
else if ((flg1 == true) && (flg2 == false))
{
flg2 = true;
_A1 = 24 + 38 * (pageNum - 1);
_A2 = 4;
_A3 = 38 + 38 * (pageNum - 1);
_A4 = 29;


}
else if ((flg1 == true) && (flg2 == true))
{
flg1 = false;
_A1 = 24 + 38 * (pageNum - 1);
_A2 = 30;
_A3 = 38 + 38 * (pageNum - 1);
_A4 = 55;

}
else if ((flg1 == false) && (flg2 == true))
{
pageNum++;
flg2 = false;

_A1 = 9 + 38 * (pageNum - 1);
_A2 = 4;
_A3 = 23 + 38 * (pageNum - 1);
_A4 = 29;

}

_P1 = _A1;
_P2 = _A2 + 4;

Marshal.ReleaseComObject(sheet.Cells[_P1, _P2]);
Marshal.ReleaseComObject(sheet.Cells[_A1, _A2]);
Marshal.ReleaseComObject(sheet.Cells[_A3, _A4]);

}


Marshal.ReleaseComObject(sheet);
sheet = null;
Marshal.ReleaseComObject(ExcelWorkbook);
ExcelWorkbook = null;
ExcelApp.Quit();
Marshal.ReleaseComObject(ExcelApp);
ExcelApp = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
}
}



No102828 (魔界の仮面弁士 さん) に返信
> ■No102826 (Massa さん) に返信
>>Excelのセル範囲を一定間隔でずらしながら,画像として出力するプログラムをC#で作成しています.
> ReleaseComObject 関連の解放処理がゴッソリ抜け落ちてますね…。
>
> C# からアウトプロセスで呼び出しているものなので、解放漏れがあると
> Quit() 後も Excel のインスタンスが非表示で残り続ける要因に繋がりますよ。
>
>
>>range.CopPictureのところでエラーになるときとならないときがあり,不安定なってしまいます.
> スペルミスに目をつぶるとしても、提示されたコードは
>  「{」が 11 個、
>  「}」が 9 個
> という非対称なものであり、そもそもコンパイルが通りそうにありません…。
>
>
>>エラーメッセージは「range クラスの copypicture メソッドが失敗しました。」と表示されます.
> クリップボードが使用中などで処理できなかった場合に、そのようなエラーが発生することがあります。
> 事象としては、Excel VBA だけで実施した場合にも起きえます。
>
> 他のアプリがクリップボード操作している場合や、PC のパフォーマンスが落ちている場合、
> あるいは、クリップボードを連続して操作した場合などに起きやすいという説もありますが、
> 根本的な対策は無いので、遅延処理やリトライなどを独自に組み込んで対処している方が多いようです。
>
> https://answers.microsoft.com/ja-jp/msoffice/forum/all/%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%A8%E3%83%A9/63090c23-3e6c-4855-80c5-d9e8684972c8
> https://social.msdn.microsoft.com/Forums/ja-JP/35487cfb-26a1-465a-bc5e-a331d98f7a4e/123041237225945310341236712384123731235612305excel2016?forum=vbajp
> https://vba-create.jp/vba-method-copypicture/
>
>
>>解決法についてご教示いただければ幸いです.
> 失敗した場合は、いったん現状の Click イベントを return で一時的に抜けて、
> 残りの処理を、別イベント (たとえば Application.Idle 時など)で実施するようリトライ処理を組み込んでみるとか。
引用返信 編集キー/
■102831 / inTopicNo.6)  Re[3]: エクセルのセルの画像化時のエラーについて
□投稿者/ とっちゃん (810回)-(2024/01/13(Sat) 02:51:15)
No102830 (Massa さん) に返信
> ありがとうございます.
>
> ReleaseComObjectについては理解ができていませんでした.

残念ですが、これでは全然足りません。

Excel(WordやPowerPointなどほかのOffice製品も同様)のオートメーションオブジェクトは
細かくインスタンスが分かれているため、「.」をつけてアクセスするオブジェクト(obj.propなら、objの部分)は
ReleaseComObjectを呼び出す必要があります。

そのため、例えば

> Microsoft.Office.Interop.Excel.Workbook ExcelWorkbook = ExcelApp.Workbooks.Open(excelPath);

の部分は、
ExcelApp.Workbooks の戻り値もあとで ReleaseComObjectを呼び出す必要があります。
(workbooksを使わないのなら、workbookを取得した直後でReleaseComObjectを呼び出しても問題ない)

ほかにも、ExcelWorkbook.Sheets も解放が必要ですし、sheet.Cells も解放が必要です。


> また,C#での遅延処理は,Sleepなどでしょうか.
>
> 以下のようにプログラムを変更して,
> エラーが出るCopyPictureの周りでSleepを入れたのですが,
> 状況の改善は見られませんでした.
>
こちらですが、魔界の仮面弁士さんがリンクを貼ってくれた記事
https://vba-create.jp/vba-method-copypicture/」にもありますが
DoEvents()(C#なら、Application.DoEvents()) を呼び出して、メッセージを処理する必要があります。

COM(オートメーションや、OLEも含む)のプロセス間通信はWindowsのメッセージも使って処理するため
メッセージを処理できる必要があります。そのため、UIスレッドで動かす必要があるという制約があります。

今回のような場合、ループの最後の ReleaseComObject をやった直後あたりで
Application.DoEvents() などを挟むのが良いと思います。

ただし、このメソッドを呼び出すとメッセージを処理するため副作用が出る可能性があります。
その点も注意が必要です。

引用返信 編集キー/
■102834 / inTopicNo.7)  Re[4]: エクセルのセルの画像化時のエラーについて
□投稿者/ Massa (5回)-(2024/01/14(Sun) 08:18:01)
ご指摘いただき,ありがとうございました.

まだプログラミングを始めたばかりで,
とても勉強になります.

Application.DoEvents() とリトライ処理を入れ,
想定通りに動くものを作ることができました.

解法処理はまだ理解が不十分ですが,
一つずつ処理をしていくのかなと考え,処理を入れていきます.

本当にありがとうございました.



No102831 (とっちゃん さん) に返信
> ■No102830 (Massa さん) に返信
>>ありがとうございます.
>>
>>ReleaseComObjectについては理解ができていませんでした.
>
> 残念ですが、これでは全然足りません。
>
> Excel(WordやPowerPointなどほかのOffice製品も同様)のオートメーションオブジェクトは
> 細かくインスタンスが分かれているため、「.」をつけてアクセスするオブジェクト(obj.propなら、objの部分)は
> ReleaseComObjectを呼び出す必要があります。
>
> そのため、例えば
>
>> Microsoft.Office.Interop.Excel.Workbook ExcelWorkbook = ExcelApp.Workbooks.Open(excelPath);
>
> の部分は、
> ExcelApp.Workbooks の戻り値もあとで ReleaseComObjectを呼び出す必要があります。
> (workbooksを使わないのなら、workbookを取得した直後でReleaseComObjectを呼び出しても問題ない)
>
> ほかにも、ExcelWorkbook.Sheets も解放が必要ですし、sheet.Cells も解放が必要です。
>
>
>>また,C#での遅延処理は,Sleepなどでしょうか.
>>
>>以下のようにプログラムを変更して,
>>エラーが出るCopyPictureの周りでSleepを入れたのですが,
>>状況の改善は見られませんでした.
>>
> こちらですが、魔界の仮面弁士さんがリンクを貼ってくれた記事
> 「https://vba-create.jp/vba-method-copypicture/」にもありますが
> DoEvents()(C#なら、Application.DoEvents()) を呼び出して、メッセージを処理する必要があります。
>
> COM(オートメーションや、OLEも含む)のプロセス間通信はWindowsのメッセージも使って処理するため
> メッセージを処理できる必要があります。そのため、UIスレッドで動かす必要があるという制約があります。
>
> 今回のような場合、ループの最後の ReleaseComObject をやった直後あたりで
> Application.DoEvents() などを挟むのが良いと思います。
>
> ただし、このメソッドを呼び出すとメッセージを処理するため副作用が出る可能性があります。
> その点も注意が必要です。
>
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ