質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Q&A

解決済

4回答

4587閲覧

ExcelVBAで取消線が入っている文字列を削除する際に無限ループに陥る

Zen_then

総合スコア34

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

1グッド

0クリップ

投稿2019/02/27 17:52

前提・実現したいこと

指定したセル内の文字列について、取消線が含まれる文字だけを削除するマクロを作成しています。
ソースコードについては下記の通りです。

Public Sub removeStrikethrough() Dim tmpRange As Range Dim i, tmpLength As Long For Each tmpRange In Selection '取消線を含む文字列を削除 tmpLength = Len(tmpRange.MergeArea(1).Value) i = 1 Do While i <= tmpLength If tmpRange.MergeArea(1).Characters(i, 1).Font.Strikethrough Then tmpRange.MergeArea(1).Characters(i, 1).Delete Else i = i + 1 End If tmpLength = Len(tmpRange.MergeArea(1).Value) Loop Next tmpRange End Sub

 機能の動作として「取消線が含まれていない文字列の書式設定はそのまま残す」ことを前提にしており、Characters.Deleteメソッドを使用して1文字ずつ削除しようとしています。
また、結合セル・結合されていないセルを問わず処理を行いたいです。

発生している問題・エラーメッセージ

 この時、様々な桁数で検証していたところ、100文字、200文字の場合は期待通りの結果を得られたのですが300文字でテストしたところ、Characters.DeleteメソッドはVBAでデバッグ(ステップイン)で確かに処理されているように見えるのですが、実際にはDeleteは実行されず、エラーも出ないまま処理を終えてしまいます。(画像:左 実行前、右 実行後)
そのためLen(tmpRange.MergeArea(1).Value)で桁数を再度カウントしても、文字が実際には削除されていないので無限ループに陥ります。
何か良い対策はないかご教示いただけると幸いです。
イメージ説明
画像補足:
1つめ 結合セルで100文字
2つめ 結合しないセルで100文字
3つめ 結合セルで200文字
4つめ 結合しないセルで100文字
5つめ 結合セルで300文字
6つめ 結合しないセルで100文字

補足情報(FW/ツールのバージョンなど)

・Excel Office365 for MSO 32bit
・Windows10 Home

mkdsk👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

Excelは256文字の制限があるようですね。
同じように困っている記事。
https://answers.microsoft.com/ja-jp/msoffice/forum/all/excel%E3%81%AErangecharactersinsertdelete%E3%81%8C/9dc4e053-e3ae-4494-8f37-669b4304d35e

回避策がどうにも思いつかなく。。。
とりあえず無限ループは避けるようには出来ましたが。

VBA

1Public Sub removeStrikethrough() 2 Dim tmpRange As Range 3 Dim i, tmpLength As Long 4 For Each tmpRange In Selection 5 6 '取消線を含む文字列を削除 7 tmpLength = Len(tmpRange.MergeArea(1).Value) 8 9 '後ろから処理していくことで無限ループを回避 10 i = tmpLength 11 Do While i >= 1 12 If tmpRange.MergeArea(1).Characters(i, 1).Font.Strikethrough Then 13 tmpRange.MergeArea(1).Characters(i, 1).Delete 14 '空文字置き換えたらどうかと思ったけど、反映されないようです(256文字以内ならこの方法でもOK) 15 'tmpRange.MergeArea(1).Characters(i, 1).Text = "" 16 End If 17 18 i = i - 1 19 Loop 20 Next tmpRange 21End Sub 22

追記
なんか気になって寝ながらコードを考えたので書いてみました。
参考まで。

VBA

1Public Sub removeStrikethrough() 2 3 Debug.Print Now 4 5 Application.DisplayAlerts = False 6 7 Dim tmpRange As Range 8 Dim i, j, tmpLength As Long 9 10 Dim ash As Worksheet ' セルが選択されているシート 11 Dim wsh As Worksheet ' 作業用シート 12 Dim wrg As Range ' 作業用セル 13 14 Set ash = ActiveSheet 15 Set wsh = ActiveWorkbook.Worksheets.Add 16 Set wrg = wsh.Cells(1, 1) 17 ash.Select 18 19 For Each tmpRange In Selection 20 21 tmpLength = Len(tmpRange.MergeArea(1).Value) 22 23 ' 対象セルを作業用セルにコピー 24 tmpRange.Copy wrg 25 26 ' 取消し線以外の文字を収集 27 tmpv = "" 28 For i = 1 To tmpLength 29 With wrg.Characters(i, 1) 30 If Not .Font.Strikethrough Then 31 tmpv = tmpv & .Caption 32 End If 33 End With 34 Next 35 36 ' 取消し線以外の文字で更新 37 tmpRange.Value = tmpv 38 39 ' 取消し線以外の文字のフォント情報をコピー 40 j = 1 41 For i = 1 To tmpLength 42 With wrg.Characters(i, 1) 43 If Not .Font.Strikethrough Then 44 tmpRange.Characters(j, 1).Font.Bold = .Font.Bold 45 tmpRange.Characters(j, 1).Font.Color = .Font.Color 46 tmpRange.Characters(j, 1).Font.ColorIndex = .Font.ColorIndex 47 tmpRange.Characters(j, 1).Font.FontStyle = .Font.FontStyle 48 tmpRange.Characters(j, 1).Font.Italic = .Font.Italic 49 tmpRange.Characters(j, 1).Font.Size = .Font.Size 50 tmpRange.Characters(j, 1).Font.Underline = .Font.Underline 51 j = j + 1 52 End If 53 End With 54 Next 55 Next tmpRange 56 57 wsh.Delete 58 59 Application.DisplayAlerts = True 60 61 Debug.Print Now 62 63End Sub

投稿2019/02/28 01:00

編集2019/03/01 00:43
ttyp03

総合スコア17000

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zen_then

2019/02/28 15:18

散々ググってもこの記事にはたどり着くことができませんでした。 参考記事を教えていただき、問題点がはっきりして大変助かりました。ありがとうございます!
jawa

2019/03/01 01:34 編集

・取消線の文字を削除した文字列(書式なし)を作成し、元の内容から取消線の文字だけ読み飛ばして書式をコピーする これは私も実装中に視野に入れていたのですが、長さの違う文字列に対して書式を移していくというのが何となく引っかかるというか、インデックスがずれたりしたら面倒・・という理由で見送っていました。 実際できたものを見ると、フォントコピーがかなりスッキリしますね。 私のサンプルコードでは何度も書式をコピーしていましたが、それが1回で済むのは速度面でも大きなメリットだと思います。 「文字を削る」タイミングと「書式を移す」タイミングで別々に取り消し線の判定を行っているだけで、「元が同じで、削る条件も同じ」なので問題ないと思いますし、これも選択肢としてアリだと思いました。
ttyp03

2019/03/01 01:35

やることはシンプルなのでそれほど複雑にはならないですね。 ただどうしてもループが2回発生してしまうのが残念です。
guest

0

ベストアンサー

処理されない原因

まず、処理できない原因はCharactersで取り扱える文字数が256文字までだからです。
257文字以降も取得はできるしエラーにもならないのでわかり難いのですが、257文字以上の文字列を操作しても結果は保障されません。
※フォントの適用など、できるものはできるようです。

というわけで、まず処理されない原因はこれだと思います。

回避策としては、256文字以上のセルは一旦他のセルに256文字分だけ取り出して処理し、結果を連結したうえでフォントを1文字ずつ適用する、といった具合でしょうか。

ループの問題

もうひとつ、これはテクニックになるのですが、今回のように「文字を削除していく」とか「行を削除していく」といったように「対象を削っていくループ処理」を前方から順に処理していくと、ループをしていく中で対象のインデックスがずれていくことになります。
このような場合は、ループを逆順で回した方が安全かつ楽なコーディングで処理できます。
今回の場合でいうと、逆順ループにしていれば、少なくとも無限ループに陥ることはありませんでした。

For i = tmpLength To 0 Step -1 If tmpRange.MergeArea(1).Characters(i, 1).Font.Strikethrough Then tmpRange.MergeArea(1).Characters(i, 1).Delete End If Next

といった具合です。
こうすることで、ループの中で文字(例えば10文字中の7文字目)を削っても、それ以降に処理する文字列(6文字目より前)には影響を与えないので、独自にカウンタを取る必要がなくなります。
これにより条件を満たすまでループするDo~Loopではなく、For~Nextの有限ループで記述できるので無限ループのリスクを回避できるというわけです。

参考になれば幸いです。

(追記)

256文字ずつ取り出して処理する方法ですが、かなり面倒なことになってしまいました。
もう少しシンプルにできないものでしょうかね・・。

Sub removeStrikethrough() Dim tmpRange As Range Dim i, tmpLength As Long Dim iCnt As Integer Dim iIdx As Integer Dim cWk1 As Range '作業用①:256文字ずつ取り出すセル Dim cWk2 As Range '作業用②:処理結果を連結するセル Dim cOrg As Range '作業用③:元の内容を保管するセル Set cOrg = Cells(1, 1) '使っていないセル(A1) Set cWk1 = Cells(2, 1) '使っていないセル(A2) Set cWk2 = Cells(3, 1) '使っていないセル(A3) '速度向上のため処理中の画面更新をOFF Me.Application.ScreenUpdating = False For Each tmpRange In Selection '結合セルは先頭セルでのみ処理する If tmpRange.Address <> tmpRange.MergeArea(1).Address Then Exit For tmpLength = Len(tmpRange.MergeArea(1).Value) '元のセルを作業用③セルにコピーする tmpRange.Copy cOrg '元のセルはクリアする(結果を出力するため) tmpRange = "" '後ろから256文字ずつ取り出すループ For iCnt = Int((tmpLength - 1) / 256) To 0 Step -1 '作業用③から①に256文字を転記 cWk1 = cOrg.Characters(1 + (256 * iCnt), 256).Caption '作業用③セルから作業用①セルへ書式を反映 For i = 1 To 256 iIdx = i + (256 * iCnt) If iIdx > tmpLength Then Exit For 'フォントのコピー Call prcCopyFont(cWk1, i, cOrg.Characters(iIdx, 1).Font) Next i '作業用①セルから取り消し線の文字を削除する For i = Len(cWk1) To 0 Step -1 If cWk1.Characters(i, 1).Font.Strikethrough Then cWk1.Characters(i, 1).Delete End If Next '元セル(現時点での結果セル)の内容を作業用②セルに出力する tmpRange.Copy cWk2 '元セル(現時点での結果セル)に作業用①と作業用②の内容を連結する。 tmpRange = cWk1.Text & cWk2.Text '作業用①の書式を反映する For iIdx = 1 To Len(cWk1) 'フォントのコピー Call prcCopyFont(tmpRange, iIdx, cWk1.Characters(iIdx, 1).Font) Next '作業用②の書式を反映する For iIdx = 1 To Len(cWk2) 'フォントのコピー Call prcCopyFont(tmpRange, iIdx + Len(cWk1), cWk2.Characters(iIdx, 1).Font) Next Next iCnt Next cOrg.Clear cWk1.Clear cWk2.Clear '画面更新をON Me.Application.ScreenUpdating = True End Sub 'フォントをコピーする関数 Sub prcCopyFont(ByRef vCell As Range, ByVal vIndex As Integer, ByVal vFont As Font) With vCell.Characters(vIndex, 1).Font .Bold = vFont.Bold .Color = vFont.Color .Italic = vFont.Italic .Name = vFont.Name .Size = vFont.Size .Underline = vFont.Underline .Strikethrough = vFont.Strikethrough End With End Sub

投稿2019/02/28 01:32

編集2019/02/28 05:41
jawa

総合スコア3020

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

jawa

2019/02/28 01:33

逆順ループの提案までttyp03さんと内容ほぼ被りでした。。
jawa

2019/02/28 05:44 編集

256文字ずつ切り出して処理する方法を追記してみましたが、文字単位の書式コピーが面倒なうえにかなり重い処理になってしまいますね。 作業用に、使ってないセルを3つも利用してますし。 一応、参考までにということで。
ttyp03

2019/02/28 06:23

おー、すごいです。 けどかなり重いですね。 オブジェクトのディープコピーができれば楽なんでしょうけどなかなか厳しいですね。
jawa

2019/02/28 07:13 編集

そうなんです。 `CellA.Characters(1,0).Insert (CellB.Characters(1,256))`みたいなかんじで、CharactersからCharactersへ書式付きで切り貼りできればラクなんですが。。
Zen_then

2019/02/28 15:11

ありがとうございます! ループの組み方のご指摘から代替案の提示まで本当に勉強になりました。 ワーク用セルを3つ使用しないといけない点は、空きセルの位置をどこか決める必要がある点では確かに悩ましいところですね。 自身の環境に合わせて実装を検討させていただきますm(_ _)m
ttyp03

2019/03/01 00:46

もう解決されていますが、私の方でもコードを書いてみました。 ワーク用セルは必須ですが、私の案ではシートを一時的に作成することで回避しています。 参考まで。
guest

0

Strikethrough=Falseにしてみて下さい。

VBA

1 Do While i <= tmpLength 2 If tmpRange.MergeArea(1).Characters(i, 1).Font.Strikethrough Then 3 tmpRange.MergeArea(1).Characters(i, 1).Font.Strikethrough = False 4 tmpRange.MergeArea(1).Characters(i, 1).Text = "" 5 Else 6 i = i + 1 7 End If 8 tmpLength = Len(tmpRange.MergeArea(1).Value) 9 Loop

Deleteではなく空文字で置き換えると上手く動作しました。

投稿2019/02/28 01:31

編集2019/02/28 01:58
sazi

総合スコア25430

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sazi

2019/02/28 15:56 編集

無限ループはしなくなるけど、Deleteで文字が削除されませんね・・ しかも、かなり時間が掛かってしまう。
sazi

2019/02/28 15:56 編集

空文字での置き換えに修正しました。
Zen_then

2019/02/28 15:14

回答ありがとうございます!私の環境で試してみましたが残念ながら文字が削除されませんでした。 結果こそ伴いませんでしたが、ソースを見たときに思わず「なるほど!」と感嘆しました^^
sazi

2019/02/28 15:55

そうなんですか。 まあ、確からしい理由があってのものではないですから。
guest

0

一文字ずつ消すと処理時間がかかるので一度変数に入れてから処理をされてみてはいかがでしょうか?
取り消し線が引かれていない文字のみを変数に格納して必要箇所に出力するとか…

VBA

1Sub test() 2 3 Dim i As Long 4 Dim s As String 5 Dim r As Range 6 7 Set r = Range("B2") 8 For i = 1 To r.Characters.Count 9 If Not r.Characters(i, 1).Font.Strikethrough Then 10 s = s & r.Characters(i, 1).Caption 11 End If 12 Next 13 14End Sub

投稿2019/02/28 00:42

ukkari-ukachan

総合スコア50

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ttyp03

2019/02/28 00:45

取消し線以外の文字の書式がクリアされてしまいませんか?
ukkari-ukachan

2019/02/28 00:46

あー、確かにそこは想定していませんでした… ご指摘有難う御座います!
Zen_then

2019/02/28 15:21

回答ありがとうございます! そうなんですよね>< 書式消えちゃうんです。 書式なんてコダワリが無ければどれだけ楽できたことか…と思いながらも捨てがたいんですよね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問