前回までの話。
申請承認ダイアログ作成
さて、このプロジェクトで最後になるダイアログを作成します。
ダイアログ追加手順はいつも通り。
リソースビューからDialogを右クリック、リソースの追加で新規作成します。
ダイアログ名はIDD_DIALOG_APPROVALにしておきます。
今回必要なコントロールは、
リストコントロールと、登録ボタン(OKボタン)とキャンセルボタンだけです。
では、適当に配置。
今回は少し横に長くしてみました。

リストコントロールのビューは「レポート」にしています。
もしかしたら、幅は後で調整するかもしれませんが、一応これで。
各IDや配置は以下の通り。
IDD_DIALOG_APPROVAL DIALOGEX 0, 0, 370, 176
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "登録",IDOK,240,155,50,14
PUSHBUTTON "戻る",IDCANCEL,300,155,50,14
CONTROL "",IDC_LIST_APPROVAL,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,15,20,340,125
END続いてクラスの追加。
適当にコントロールをダブルクリックします。
今回のクラス名やファイル名はこの通り。

OKを押し、追加します。
最後に、クラスビューからCApprovalを選択。
プロパティーのオーバーライドから、OnInitDialogをAddします。
ついでに、OKボタン(登録ボタン)とし、
実際のダイアログでクリック時に呼ばれる関数を追加します。
そしてリストコントロール。
いつもならこいつもリソースビューからダブルクリックで関数自動追加しますが、
ちょっとメッセージ種類を変えます。
右クリックから「イベントハンドラーの追加」を選択。
メッセージの種類を「NM_CLICL」にします。
デフォルトだとクラスがCAboutDlgになっているので、CApprovalに変更します。
(これに気付かず3回試して「なんで違うクラスに関数生成されるんだ??って悩んでました…」)


まぁ正直なところ、メッセージ種類がデフォルトのLVM_ITEMCHANGEDと何が違うのかはあまりわかっていません。
こっちにしたら上手いことできそうだったので、紹介しているだけです。
機会があればまたお勉強…。
上記ID名だと、Approval.hにこんな感じで追加されている状況です。
public:
virtual BOOL OnInitDialog();
afx_msg void OnBnClickedOk();
afx_msg void OnNMClickListApproval(NMHDR* pNMHDR, LRESULT* pResult);動作処理作成
リストコントロール表示変更処理
ひとまず、リストコントロール内に変更を加えることができるかの確認から。
Approval.hに変数を一つ追加。
private:
CListCtrl m_ctrlListApproval;Approval.cppへ移ります。
DoDataExchangeで紐づけします。
void CApproval::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST_APPROVAL, m_ctrlListApproval);
}そしてOnInitDialogへ。
列は仕様通りにしますが、データはテスト用に作成したものを埋めます。
BOOL CApproval::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: ここに初期化を追加してください
// リストコントロールの列作成
m_ctrlListApproval.InsertColumn(0, _T("Id"), LVCFMT_LEFT, 50);
m_ctrlListApproval.InsertColumn(1, _T("ユーザ"), LVCFMT_LEFT, 200);
m_ctrlListApproval.InsertColumn(2, _T("グループ"), LVCFMT_LEFT, 200);
m_ctrlListApproval.InsertColumn(3, _T("項目"), LVCFMT_LEFT, 200);
m_ctrlListApproval.InsertColumn(4, _T("承認"), LVCFMT_LEFT, 50);
m_ctrlListApproval.InsertColumn(5, _T("拒否"), LVCFMT_LEFT, 50);
LVITEM lvItem{};
lvItem.mask = LVIF_TEXT;
// ↓↓↓テスト用ここから↓↓↓
for (int i = 1; i < 6; i++)
{
lvItem.iItem = i - 1; // 行番号
for (int j = 1; j < 7; j++)
{
int iTemp = 10 * i + j;
lvItem.iSubItem = j - 1;
CString strTemp;
if (j != 5 && j != 6)
{
strTemp.Format(_T("%d"), iTemp);
}
lvItem.pszText = strTemp.GetBuffer();
if (j == 1)
{
m_ctrlListApproval.InsertItem(&lvItem);
}
else
{
m_ctrlListApproval.SetItem(&lvItem);
}
}
}
// ↑↑↑テスト用ここまで↑↑↑
SetWindowText(_T("申請状態"));
UpdateData(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
// 例外 : OCX プロパティ ページは必ず FALSE を返します。
}テスト用データは適当です。
5列目と6列目は空欄にしておきます。
そして最後に、リストコントロールがクリックされた時に呼ばれる処理。
void CApproval::OnNMClickListApproval(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
// TODO: ここにコントロール通知ハンドラー コードを追加します。
LVHITTESTINFO hitTestInfo;
CPoint point = pNMItemActivate->ptAction; // クリックされた位置
hitTestInfo.pt = point;
int nRow = m_ctrlListApproval.SubItemHitTest(&hitTestInfo); // 行番号の取得
int nCol = 0; // 列番号初期化
if (nRow != -1)
{
nCol = hitTestInfo.iSubItem; // 列番号の取得
if (nCol == 4)
{
// 「承認」列がクリックされた
// 文字列を取得する
CString strTextApp = m_ctrlListApproval.GetItemText(nRow, nCol);
if (strTextApp == _T(""))
{
// 空欄の場合は、「レ」に変更
m_ctrlListApproval.SetItemText(nRow, nCol, _T("レ"));
if (m_ctrlListApproval.GetItemText(nRow, nCol + 1) == _T("レ"))
{
// 同行の「拒否」列にチェックが入っていたら、空欄に戻す
m_ctrlListApproval.SetItemText(nRow, nCol + 1, _T(""));
}
}
else if (strTextApp == _T("レ"))
{
// チェック済みの場合は空欄に戻す
m_ctrlListApproval.SetItemText(nRow, nCol, _T(""));
}
}
else if (nCol == 5)
{
// 「拒否」列がクリックされた
// 文字列を取得する
CString strTextApp = m_ctrlListApproval.GetItemText(nRow, nCol);
if (strTextApp == _T(""))
{
// 空欄の場合は、「レ」に変更
m_ctrlListApproval.SetItemText(nRow, nCol, _T("レ"));
if (m_ctrlListApproval.GetItemText(nRow, nCol - 1) == _T("レ"))
{
// 同行の「承認」列にチェックが入っていたら、空欄に戻す
m_ctrlListApproval.SetItemText(nRow, nCol - 1, _T(""));
}
}
else if (strTextApp == _T("レ"))
{
// チェック済みの場合は空欄に戻す
m_ctrlListApproval.SetItemText(nRow, nCol, _T(""));
}
}
}
*pResult = 0;
}その行に対し、「承認」列にチェックを入れるのか、「拒否」列にチェックを入れるのかの処理を入れます。
一度ビルドし、これでどんな動きになるか確認です。
承認ダイアログを開きます。

ここで適当に承認列または拒否列をクリックすると。

ちゃんとチェックマークがついてくれました。
必ず、一つの行にどちらかしかチェックが入らないようにもなっています。
既存処理修正
リストコントロールの動きは確認できたので、
実際に処理を書いていきます。
グループや項目の方法と同様、マップを使っていきます。
以前、メイン画面のリストコントロールに表示させる手法として、
構造体を定義してテーブルからマップを作成しました。
構造体RecordValueを今回も使用します。
が、二つ値を付け加えます。
構造体を定義したヘッダー、SupplyRequestManager.hを開きます。
そこに、ユーザIDとユーザ名の情報を追加します。
struct RecordValue
{
CString strGroupName; // グループ名称
CString strKoumokuName; // 項目名称
CString strPrice; // 項目値段
CString strApproval; // 承認状態
CString strUserID; // ユーザID 新規追加
CString strUserName; // ユーザ名 新規追加
};続いて、recordテーブルから値を取得する関数。
これも少し変更します。
引数を増やし、ユーザ条件を付けるか付けないかの判定を行います。
まずは定義、MySQLManager.h
public:
// 申請履歴マップセット
void SetRecordMap(std::unordered_map<int, RecordValue>* MapRecord, BOOL bUserCondition);BOOL型の引数を増やしました。
この関数をソリューションで検索をかけると、
このヘッダー含めて4か所あったので順に直します。
続いてMySQLManager.cpp。
関数処理を書いている部分です。
中途半端に修正箇所を載せるとかえって見づらいので、
長いですが全文を。
/**
* @fn
* @brief 申請履歴テーブルセット
* @param std::unordered_map<int, RecordValue>* マップポインタ
* BOOL TRUE ユーザ条件あり
* FALSE ユーザ条件なし
* @return なし
*/
void CMySQLManager::SetRecordMap(std::unordered_map<int, RecordValue>* MapRecord, BOOL bUserCondition)
{
try
{
m_con->setSchema(SCHEMA);
CSupplyRequestManagerApp* pApp = static_cast<CSupplyRequestManagerApp*>(AfxGetApp());
CString strUserId = pApp->strGetUserId(); // ユーザID取得
sql::SQLString strsqlUserID = strConverter(strUserId); // ユーザID型変換
sql::SQLString strSQL = "SELECT " + TABLE_RECORD + ".Id AS RecordID, "; // recordテーブルのID
strSQL += TABLE_USER + ".Id AS UserId, "; // userテーブルのユーザID
strSQL += TABLE_USER + ".Name AS UserName, "; // userテーブルのユーザ名
strSQL += TABLE_GROUP + ".Name AS GroupName, "; // groupテーブルのグループ名称
strSQL += TABLE_KOUMOKU + ".Name AS KoumokuName, "; // koumokuテーブルの項目名称
strSQL += TABLE_KOUMOKU + ".Price AS KoumokuPrice, "; // koumokuテーブルの項目値段
strSQL += TABLE_RECORD + ".Approval AS RecordApproval "; // recordテーブルの承認状態
strSQL += "FROM " + TABLE_RECORD;
strSQL += " JOIN " + TABLE_KOUMOKU + " ON " + TABLE_RECORD + ".KoumokuId = " + TABLE_KOUMOKU + ".Id";
strSQL += " JOIN " + TABLE_GROUP + " ON " + TABLE_KOUMOKU + ".GroupId = " + TABLE_GROUP + ".Id";
strSQL += " JOIN " + TABLE_USER + " ON " + TABLE_RECORD + ".UserId = " + TABLE_USER + ".Id";
if (bUserCondition == TRUE)
{
strSQL += " WHERE " + TABLE_RECORD + ".UserId = " + strsqlUserID; // recordテーブルでユーザIDが現ユーザ
}
// クエリを実行
std::unique_ptr<sql::PreparedStatement> pstmt(m_con->prepareStatement(strSQL));
std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
// 結果を処理
if (res->next())
{
do
{
int iRecordId = 0;
CString strGroupName = _T("");
CString strKoumokuName = _T("");
CString strKoumokuPrice = _T("");
int iApproval = 0;
CString strApproval = _T("");
CString strUserID = _T("");
CString strUserName = _T("");
std::string strsqlCol = res->getString("RecordID"); // ID列の取得
CString strCol = strConvertFromUTF8ToUTF16(strsqlCol); // 型変換
iRecordId = _ttoi(strCol);
strsqlCol = res->getString("GroupName"); // グループ名称列の取得
strCol = strConvertFromUTF8ToUTF16(strsqlCol); // 型変換
strGroupName = strCol;
strsqlCol = res->getString("KoumokuName"); // 項目名称列の取得
strCol = strConvertFromUTF8ToUTF16(strsqlCol); // 型変換
strKoumokuName = strCol;
strsqlCol = res->getString("KoumokuPrice"); // 値段列の取得
strCol = strConvertFromUTF8ToUTF16(strsqlCol); // 型変換
strKoumokuPrice = strCol;
strsqlCol = res->getString("RecordApproval"); // 承認状態の取得
strCol = strConvertFromUTF8ToUTF16(strsqlCol); // 型変換
iApproval = _ttoi(strCol);
if ((ApprovalMode)iApproval == enPending)
{
strApproval = _T("申請中");
}
else if ((ApprovalMode)iApproval == enApprovaled)
{
strApproval = _T("承認済");
}
else if ((ApprovalMode)iApproval == enRejected)
{
strApproval = _T("拒否");
}
else
{
CString strMes = _T("");
strMes.Format(_T("データに問題があります。管理者へ連絡を!\r\nrecord ID:%d"), iRecordId);
AfxMessageBox(strMes);
return;
}
strsqlCol = res->getString("UserId"); // ユーザID列の取得
strCol = strConvertFromUTF8ToUTF16(strsqlCol); // 型変換
while (strCol.GetLength() < 5)
{
strCol = _T("0") + strCol; // 5桁に
}
strUserID = strCol;
strsqlCol = res->getString("UserName"); // ユーザ名称列の取得
strCol = strConvertFromUTF8ToUTF16(strsqlCol); // 型変換
strUserName = strCol;
(*MapRecord)[iRecordId] = { strGroupName, strKoumokuName, strKoumokuPrice, strApproval, strUserID, strUserName };
} while (res->next());
}
else
{
// 該当データが何もないけどそれは問題ない
}
}
catch (sql::SQLException& e)
{
// 実行できなかった
ErrSQLException(e);
return;
}
}修正箇所は
- 9行目:引数を増やした
- 20~21行目:ユーザIDとユーザ名を取得するように
- 30行目:ユーザID&ユーザ名が取れるようJOIN句追加
- 32~35行目:WHERE句の実行を、引数で判定
- 95~105行目:ユーザIDとユーザ名を取得
- 107行目:マップにユーザID(strUserId)とユーザ名(strUseName)を追加
SupplyRequestManagerDlg.cppに、
SetRecordMap関数を使っている箇所が2か所あるので、
引数を増やします。
両方ともユーザID条件は必要なので、TRUE追加です。
BOOL CSupplyRequestManagerDlg::OnInitDialog()
{
//略
GroupKoumokuGetter.SetRecordMap(GetMapRecord(), TRUE); // 履歴テーブルデータをマップに格納
//略
}
void CSupplyRequestManagerDlg::OnBnClickedButtonMainRequest()
{
// 略
GroupKoumokuGetter.SetRecordMap(GetMapRecord(), TRUE); // 履歴テーブルデータをマップに格納
// 略
}これで既存で直したいところは以上です。
申請承認ダイアログ作成
最後に、申請状況の一覧を出す処理を作ります。
Approval.hで必要な変数関数の定義。
private:
std::unordered_map<int, RecordValue> m_MapRecord; // 履歴テーブル格納
public:
std::unordered_map<int, RecordValue>* GetMapRecord() { return &m_MapRecord; }
private:
void ShowList();Approval.cppで処理を書きます。
SQLの処理を行いため、ヘッダーをインクルードします。
#include "MySQLManager.h"OnInitDialogで、初期処理を。
列の決定、リストコントロールにマップ内容を表示させる関数を呼びます。
BOOL CApproval::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: ここに初期化を追加してください
// リストコントロールの列作成
m_ctrlListApproval.InsertColumn(0, _T("Id"), LVCFMT_LEFT, 50);
m_ctrlListApproval.InsertColumn(1, _T("ユーザID"), LVCFMT_LEFT, 100);
m_ctrlListApproval.InsertColumn(2, _T("ユーザ名"), LVCFMT_LEFT, 200);
m_ctrlListApproval.InsertColumn(3, _T("グループ"), LVCFMT_LEFT, 150);
m_ctrlListApproval.InsertColumn(4, _T("項目"), LVCFMT_LEFT, 150);
m_ctrlListApproval.InsertColumn(5, _T("承認"), LVCFMT_LEFT, 50);
m_ctrlListApproval.InsertColumn(6, _T("拒否"), LVCFMT_LEFT, 50);
CMySQLManager RecordTable;
RecordTable.GetConnection(); //接続確立
RecordTable.SetRecordMap(GetMapRecord(), FALSE); // 履歴テーブルからマップへセット
ShowList();
SetWindowText(_T("申請状態"));
UpdateData(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
// 例外 : OCX プロパティ ページは必ず FALSE を返します。
}そういえば、ユーザID列を追加しました。
IDいらねぇかなぁとは思っていたら
「同姓同名あるか」と思って急遽追加…。
なお、列を一つ追加したので、
先ほどのチェックを付ける関数OnNMClickListApprovalを少しだけ変更します。
void CApproval::OnNMClickListApproval(NMHDR* pNMHDR, LRESULT* pResult)
{
//略
if (nCol == 5)
{
// 略
}
else if (nCol == 6)
{
// 略
}
//略
}列番号を変更しました。
今は個人の小規模な開発ですが、
業務でしたり大規模になったりすると、
こういうのを変数化しないとダメですよね。
最後にリストを表示させる関数です。
/**
* @fn
* @brief リスト内表示関数
* @param なし
* @return なし
*/
void CApproval::ShowList()
{
m_ctrlListApproval.DeleteAllItems(); // 念のため削除
LVITEM lvItem{};
lvItem.mask = LVIF_TEXT;
int iRow = 0; // 行番号
for (const auto& pair : m_MapRecord)
{
const RecordValue& value = pair.second;
if (pair.second.strApproval == _T("承認済") || pair.second.strApproval == _T("拒否"))
{
continue; // 未承認のものだけ表示したい
}
lvItem.iItem = iRow; // 行番号
// ID列
lvItem.iSubItem = 0;
CString strTemp;
strTemp.Format(_T("%d"), pair.first);
lvItem.pszText = strTemp.GetBuffer();
m_ctrlListApproval.InsertItem(&lvItem);
// ユーザID列
lvItem.iSubItem = 1;
strTemp = pair.second.strUserID;
lvItem.pszText = strTemp.GetBuffer();
m_ctrlListApproval.SetItem(&lvItem);
// ユーザ名列
lvItem.iSubItem = 2;
strTemp = pair.second.strUserName;
lvItem.pszText = strTemp.GetBuffer();
m_ctrlListApproval.SetItem(&lvItem);
// グループ名列
lvItem.iSubItem = 3;
strTemp = pair.second.strGroupName;
lvItem.pszText = strTemp.GetBuffer();
m_ctrlListApproval.SetItem(&lvItem);
// 項目列
lvItem.iSubItem = 4;
strTemp = pair.second.strKoumokuName;
lvItem.pszText = strTemp.GetBuffer();
m_ctrlListApproval.SetItem(&lvItem);
// 承認列 (空白)
lvItem.iSubItem = 5;
strTemp = _T("");
lvItem.pszText = strTemp.GetBuffer();
m_ctrlListApproval.SetItem(&lvItem);
// 拒否列 (空白)
lvItem.iSubItem = 6;
strTemp = _T("");
lvItem.pszText = strTemp.GetBuffer();
m_ctrlListApproval.SetItem(&lvItem);
iRow++;
}
}ここに表示させるのは未承認、
つまりrecordテーブルのApprovalが0のものだけ表示させます。
(19~22行目)
後は以前行ったのと同じ感じでリストコントロールに表示させます。
これでいったんビルド。
動作確認
現在のテーブルはこんな感じ

基本、項目テーブルをリストコントロールに表示させたものに、ユーザ情報を追加しているだけです。
exeで申請状態を出してみますと。

ちゃんと取れてました。
終わりに
今回は履歴テーブル情報を申請承認ダイアログに表示させました。
少し長くなったので、
ここから承認or拒否の登録は次回に…。
今回はここまで。

コメント