リストコントロールなどでアイテムをソートする場合、文字列としてソートしたり数字としてソートしたりすると思います。
その時、文字列としてソートする場合、比較関数内ではCStringのCompareやCompareNoCaseなどを使い、数字としてソートする場合、一旦intなどへ変換してから数値比較すると思います。
しかし、文字列の中に数字が入っていた場合、数字は文字として認識されソートされるため、数値順に並ばなかったりします。(例:Sample1.txt Sample10.txt Sample2.txt Sample3.txt)
エクスプローラーなどでは、きちんと数字部分は数値として認識されソートされるため、それと同じようにソートされるように比較関数を考えてみました。
まず最初に、比較する二つの文字列は、関数によってLPARAMだったりCStringだったりするため、ここでは使い回しが出来るように、普通にCStringとして宣言しておきます。
その都度、リストから文字列を取得したり、ポインタから文字列を取得したり、CStringにする変換処理を入れてください。
ソートフラグも状況によって取得の仕方が違うため、最後の一行の「SortFlag」は独自に書き換えてください。
ソート後の昇順降順の入れ替えなども独自に行ってください。
コードは以下のようになります。
やり方としては、文字列を先頭から比較していき、数字が出たら数値比較をし、文字の違いが出たらその時点で比較完了という流れです。
一応、全角数字も数値として扱います。
全角文字を対象にしない場合、strNumWを除外してください。
Unicode仕様です。マルチバイトの場合、少し改良が必要です。
以上、独自の方法で数字を数値としてソートするようにしたのですが、要領が悪く、アイテムが多くなると処理が遅くなりそうなので、あとはそれぞれ改良してみてください。
その時、文字列としてソートする場合、比較関数内ではCStringのCompareやCompareNoCaseなどを使い、数字としてソートする場合、一旦intなどへ変換してから数値比較すると思います。
しかし、文字列の中に数字が入っていた場合、数字は文字として認識されソートされるため、数値順に並ばなかったりします。(例:Sample1.txt Sample10.txt Sample2.txt Sample3.txt)
エクスプローラーなどでは、きちんと数字部分は数値として認識されソートされるため、それと同じようにソートされるように比較関数を考えてみました。
まず最初に、比較する二つの文字列は、関数によってLPARAMだったりCStringだったりするため、ここでは使い回しが出来るように、普通にCStringとして宣言しておきます。
その都度、リストから文字列を取得したり、ポインタから文字列を取得したり、CStringにする変換処理を入れてください。
ソートフラグも状況によって取得の仕方が違うため、最後の一行の「SortFlag」は独自に書き換えてください。
ソート後の昇順降順の入れ替えなども独自に行ってください。
コードは以下のようになります。
やり方としては、文字列を先頭から比較していき、数字が出たら数値比較をし、文字の違いが出たらその時点で比較完了という流れです。
一応、全角数字も数値として扱います。
全角文字を対象にしない場合、strNumWを除外してください。
Unicode仕様です。マルチバイトの場合、少し改良が必要です。
CString str1, str2; // 比較するための二つの変数(状況によって変換)
int iSort = 0; // ソート結果を入れる変数
int iLen1 = str1.GetLength();
int iLen2 = str2.GetLength();
int iLen = min(iLen1, iLen2); // 文字列長の短い方に合わせるため
CString strRes1, strRes2; // 数字部分を受け取るための変数
const CString strNumH = _T("0123456789"); // 半角数字を用意
const CString strNumW = _T("0123456789"); // 全角数字を用意
int iFlag; // 同じ位置に数字がある場合はその開始位置を、違う場合or無い場合は-1を入れるための変数
iFlag = (str1.FindOneOf(strNumH + strNumW) == str2.FindOneOf(strNumH + strNumW)) // 数字が最初に出てくる位置が同じかどうか
? str1.FindOneOf(strNumH + strNumW) : -1;
// 数字の開始位置が同じで、それまでの文字列も同じだった場合
if (iFlag != -1 && str1.Left(iFlag) == str2.Left(iFlag)) // 数字部分で比較
{
do
{
// 数字分以降を抽出
str1 = str1.Mid(iFlag);
str2 = str2.Mid(iFlag);
// 数字部分のみを抽出
strRes1 = str1.SpanIncluding(strNumH + strNumW);
strRes2 = str2.SpanIncluding(strNumH + strNumW);
if (strRes1 == strRes2) // 抽出した数字が同じだった場合、もう一度それより後ろについて繰り返す
{
str1 = str1.Mid(strRes1.GetLength()); ← 2015/01/01 修正
str2 = str2.Mid(strRes1.GetLength()); ← 2015/01/01 修正
iFlag = (str1.FindOneOf(strNumH + strNumW) == str2.FindOneOf(strNumH + strNumW)) ? str1.FindOneOf(strNumH + strNumW) : -1;
}
else // 数字に違いが出た場合、数値として比較してループ終了
{
iSort = (_tstoi(strRes1) < _tstoi(strRes2)) ? -1 : 1;
break;
}
} while (iFlag != -1);
}
else // 数字が出てくる前に文字列が違う場合、普通にCStringの比較関数を使用
{
iSort = lParam1.Compare(lParam2);
}
// 比較した部分が同じで、文字列長が違う場合の処理
if (iSort == 0 && iLen1 != iLen2)
iSort = (iLen1 < iLen2) ? -1 : 1;
return (SortFlag ? iSort * -1 : iSort); // 「SortFlag」は昇順降順の状況を示す値が入る
以上、独自の方法で数字を数値としてソートするようにしたのですが、要領が悪く、アイテムが多くなると処理が遅くなりそうなので、あとはそれぞれ改良してみてください。