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

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

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

仕様?誤使用?

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

■95114 / inTopicNo.1)  仕様?誤使用?
  
□投稿者/ chobi (3回)-(2020/06/24(Wed) 11:18:26)

分類:[C#] 

* 開発ツール
Microsoft Visual Studio Express 2017 for Windows Desktop
Version 15.9.23
VisualStudio.15.Release/15.9.23+28307.1146
Microsoft .NET Framework
Version 4.8.03752
* .NET Framewoek 4.6.1
* C#
* Windows10 64bit

いつもお世話になっております。
panelに張り付けたコントロール(Name==AccountElement)
をチェックが入っていたら消していくプログラムを作っています。
以下のようにforeachで列挙しながらチェック、削除していますが、
削除対象が連続すると2個目が列挙されないという現象があります。
削除しているので、削除対象controlのインッデクスが一つ減算されるのが
理由だと思いますが、使いにくいですよね・・・・
皆さんどうしているのでしょうか?

int RemoveCnt = 0;
foreach (Control ctr in Controls)
{
if (ctr.Name == "AccountElement")
{
AccountElement ae = (AccountElement)ctr;
if (ae.Checked)
{
Controls.Remove(ctr);
//++RemoveCnt;
}
//ae.Top -= ae.Height * RemoveCnt;
}
}
引用返信 編集キー/
■95115 / inTopicNo.2)  Re[1]: 仕様?誤使用?
□投稿者/ 魔界の仮面弁士 (2758回)-(2020/06/24(Wed) 11:32:56)
2020/06/24(Wed) 11:34:02 編集(投稿者)

No95114 (chobi さん) に返信
>  if (ctr.Name == "AccountElement")
>  {
Controls 内に、同じ Name を持つコントロールが複数あるのでしょうか。
デザイナー画面で貼った場合、同名コントロールは同時に存在できないハズです。
(デザイナーを用いず、コードで生成していた場合は、同名コントロールや無名コントロールも配置可能です)

もし、 "AccountElement" なコントロールがひとつだけなら、
発見した時点で break; して、ループを抜けてしまえば良いと思います。



> Controls.Remove(ctr);

ctr.Dispose(); が漏れているように見えます。

Panel に貼ったコントロールは、親フォームが閉じられたときに、
一緒に Dispose されるのですが、Remove や RemoveAt した場合は、
Controls の管理下から外れてしまうので、明示的に Dispose(); する必要があります。


> 以下のようにforeachで列挙しながらチェック、削除していますが、
> foreach (Control ctr in Controls)


// 修正案1: 後ろから前に探索するようにする
for ( int i = Controls.Count - 1 ; i >= 0 ; i-- )
{
 Control ctrl = Controls[i];


// 修正案2: Controls そのものではなく、そこから取り出した「削除前のコントロール一覧」を列挙する
foreach (var ctrl in Controls.Cast<Control>().ToArray() )
引用返信 編集キー/
■95116 / inTopicNo.3)  Re[2]: 仕様?誤使用?
□投稿者/ chobi (4回)-(2020/06/24(Wed) 11:46:57)
魔界の仮面弁士さま

適切な回答ありがとうございます。
コードで生成しているので同名です。diposeは追加しました。
とりあえず大した要素数ではなかったので、頭からやり直すようにしました。
後ろからアクセスする、事前に列挙す

        retry:
        foreach (Control ctr in Controls)
        {
            if (ctr.Name == "AccountElement")
            {
                AccountElement ae = (AccountElement)ctr;
                if (ae.Checked)
                {
                    Controls.Remove(ctr);
                    ctr.Dispose();
                    goto retry;
                }
            }
        }




■No95115 (魔界の仮面弁士 さん) に返信
> 2020/06/24(Wed) 11:34:02 編集(投稿者)
> 
> ■No95114 (chobi さん) に返信
>> if (ctr.Name == "AccountElement")
>> {
> Controls 内に、同じ Name を持つコントロールが複数あるのでしょうか。
> デザイナー画面で貼った場合、同名コントロールは同時に存在できないハズです。
> (デザイナーを用いず、コードで生成していた場合は、同名コントロールや無名コントロールも配置可能です)
> 
> もし、 "AccountElement" なコントロールがひとつだけなら、
> 発見した時点で break; して、ループを抜けてしまえば良いと思います。
> 
> 
> 
>>Controls.Remove(ctr);
> 
> ctr.Dispose(); が漏れているように見えます。
> 
> Panel に貼ったコントロールは、親フォームが閉じられたときに、
> 一緒に Dispose されるのですが、Remove や RemoveAt した場合は、
> Controls の管理下から外れてしまうので、明示的に Dispose(); する必要があります。
> 
> 
>>以下のようにforeachで列挙しながらチェック、削除していますが、
>>foreach (Control ctr in Controls)
> 
> 
> // 修正案1: 後ろから前に探索するようにする
> for ( int i = Controls.Count - 1 ; i >= 0 ; i-- )
> {
>  Control ctrl = Controls[i];
> 
> 
> // 修正案2: Controls そのものではなく、そこから取り出した「削除前のコントロール一覧」を列挙する
> foreach (var ctrl in Controls.Cast<Control>().ToArray() )

解決済み
引用返信 編集キー/
■95122 / inTopicNo.4)  Re[1]: 仕様?誤使用?
□投稿者/ ぶなっぷ (223回)-(2020/06/24(Wed) 15:42:52)
少々疑問が、
これってforeachで回しながら削除してますよね。
そーすると、
  InvalidOperationException
  「コレクションが変更されました。列挙操作は実行されない可能性があります。」
になりませんか?ならないのが不思議です。

それはさておき、修正案ですが、Controlsがいわゆるコレクション(List, Arrayy, ...)
なら、LINQメソッド(拡張メソッド)を使うのがおすすめ。

以下の1行でおしまいです。
  int RemoveCnt = Controls.RemoveAll(x => x.Name == "AccountElement");

引用返信 編集キー/
■95123 / inTopicNo.5)  Re[3]: 仕様?誤使用?
□投稿者/ PANG (2回)-(2020/06/24(Wed) 15:50:46)
No95116 (chobi さん) に返信
> Controls.Remove(ctr);

ctr.Visible = false;
がオススメです
foreach が使えるし、Disposeも気にする必要がありません。
引用返信 編集キー/
■95125 / inTopicNo.6)  Re[2]: 仕様?誤使用?
□投稿者/ 魔界の仮面弁士 (2759回)-(2020/06/24(Wed) 19:33:53)
No95122 (ぶなっぷ さん) に返信
> それはさておき、修正案ですが、Controlsがいわゆるコレクション(List, Arrayy, ...)
> なら、LINQメソッド(拡張メソッド)を使うのがおすすめ。
> 以下の1行でおしまいです。
> int RemoveCnt = Controls.RemoveAll(x => x.Name == "AccountElement");

Arrayy → Array の誤記だとしても、
Array オブジェクトに Remove 系のメソッドは用意されていないのでは…?


List<> であれば RemoveAll がありますが、ここでいう Controls は、
Panel コントロールのプロパティのようなので、恐らくは Control.ControlCollection でしょう。
RemoveAll は使えないと思います。Remove / RemoveByKey / RemoveAt だけ。



LINQ で処理するとしたら、これでどうでしょう。
変数 target を省略すれば 1 行になりますが、そこまで圧縮すると、流石に読みにくそう。

AccountElement[] target = Controls.OfType<AccountElement>().Where(c => c.Name == "AccountElement").ToArray();
Array.ForEach(target, c => { using (c) { Controls.Remove(c); } });
引用返信 編集キー/
■95136 / inTopicNo.7)  Re[3]: 仕様?誤使用?
□投稿者/ ぶなっぷ (224回)-(2020/06/25(Thu) 09:17:44)
確かに、よく読むとPanelコントロールのControls プロパティのようですね。
であれば、おっしゃられる通り、RemoveAll()はないですね。
失礼しました。

ちなみに、示してくれたサンプル
> AccountElement[] target = Controls.OfType<AccountElement>().Where(c => c.Name == "AccountElement").ToArray();
> Array.ForEach(target, c => { using (c) { Controls.Remove(c); } });
において、
using (c) {}
は何をやっているのでしょう?
引用返信 編集キー/
■95137 / inTopicNo.8)  Re[4]: 仕様?誤使用?
□投稿者/ ぶなっぷ (225回)-(2020/06/25(Thu) 09:22:06)
書いてて、気づきました。
Dispose()用ですね (;^_^A

引用返信 編集キー/

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


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

このトピックに書きこむ