●で、結局iPhoneアプリを開発、公開するには何が必要なのさ?という話
はじめに
iPhoneアプリなんぞを公開していると、「どーやって作るの?」「何が必要なの?」なんて事を訊かれる事もしばしば。
備忘録も兼ねて、iPhoneアプリの開発、公開に必要なものを以下にまとめておこうと思います。
はじめに
iPhoneアプリなんぞを公開していると、「どーやって作るの?」「何が必要なの?」なんて事を訊かれる事もしばしば。
備忘録も兼ねて、iPhoneアプリの開発、公開に必要なものを以下にまとめておこうと思います。
ちょっくらエクセルのマクロ(VBA)でも覚えようかなってことで、覚えたことのメモ。
シートを削除する関数。
'-------------------------------------------------------------------------------
' シートの削除
'-------------------------------------------------------------------------------
Sub DeleteSheet(strSheet As String)
' strSheet : 削除したいシート名
' シート内のA1セルを参照し、エラーにならないならシートが存在するので削除
If Not IsError(Evaluate("'" & strSheet & "'!A1")) Then
' 削除確認を一時的に解除
Application.DisplayAlerts = False
' 削除
Worksheets(strSheet).Delete
' 削除確認を元に戻す
Application.DisplayAlerts = True
End If
End Sub
'-------------------------------------------------------------------------------
なかなか便利だねぇ、マクロ。
リストビューの列項目でユーザーに見せたくない項目がある場合、リストビューのヘッダの幅を 0 で固定する必要がある。リストビューのヘッダの幅を固定するには、リストビューをサブクラス化し、WM_NOTIFYとしてHDN_BEGINTRACK, HDN_DIVIDERDBLCLICK が飛んできた場合に TRUE を返す。ただし、環境によっては UNICODE 環境じゃないのに HDN_BEGINTRACKW, HDN_DIVIDERDBLCLICKW が飛んでくる事があるので、HDN_BEGINTRACKW, HDN_BEGINTRACKA, HDN_DIVIDERDBLCLICKW, HDN_DIVIDERDBLCLICKA を全て禁止する。
LRESULT OnNotify(int idFrom, NMHDR* pnmhdr)
{
// ヘッダの幅を変更できないようにする
// UNICODE環境じゃなくてもHDN_*Wが飛んでくる場合があるようなので、
// HDN_*A, HDN_*W 両方とも禁止
HD_NOTIFY * phdn = (HD_NOTIFY *)pnmhdr;
switch(phdn->hdr.code)
{
case HDN_BEGINTRACKA:
case HDN_BEGINTRACKW:
case HDN_DIVIDERDBLCLICKA:
case HDN_DIVIDERDBLCLICKW:
if(phdn->iItem == 1)
return TRUE;
break;
}
return CListView::OnNotify(idFrom, pnmhdr);
}
より完璧にするとしたら、境界線にマウスが来た際にマウスカーソルが変更されるのも阻止する。
ホットキーコントロールには入力制限があり、スペース、エンター、タブキーなどを受け付けてくれない。これを解除しようとすると、ホットキーコントロールをサブクラス化して、WM_KEYDOWNメッセージが飛んできた際にHKM_SETHOTKEYをホットキーコントロールに対して投げてやる必要がある。
virtual void OnKeyDown(UINT vk, int cRepeat, UINT flags)
{
if(vk == VK_RETURN || vk == VK_TAB || vk == VK_SPACE || vk == VK_DELETE || vk == VK_ESCAPE || vk == VK_BACK)
{
SendMessage(m_hWnd, HKM_SETHOTKEY, MAKEWORD(vk, HIBYTE(SendMessage(m_hWnd, HKM_GETHOTKEY, 0, 0))), 0);
return;
}
CHotKeyCtrl::OnKeyDown(vk, cRepeat, flags);
}
まあこんな感じ。
管理者IDとパスワードを忘れない為に、早速日記を書く。
年末から更新してきた聞々ハヤえもんだが、そろそろキーカスタマイズ機能をつけることにする。以下の流れで開発を行っていく。
(1)動的にアクセラレータテーブルを管理する CAcceleratorTable クラスを作る。
※メンバ関数として、Create, Destroy, Add, Delete 関数を持つ。
(2)キーボードショートカットの選択にホットキーコントロールの使用を考えている為、CHotKeyCtrl クラスを作る。
※デフォルトのホットキーコントロールでは、スペースキー等入力不可なキーがあるのでサブクラス化できるようにしておく事。
(3)キーカスタマイズ用ウィンドウを管理する CKeySettingWnd_MainWnd クラスを作る。
※CFrameWnd クラスを継承する事。
(4)「システム→キー操作」メニューを追加。
※修正対象は、CMenu_MainWnd クラスと CMainWnd クラス。
これが済んだらホットキーに対応して、ウィンドウが非アクティブな場合でも操作ができるようにする。
さーて、作るぞ。
というわけで、まず、「(1)動的にアクセラレータテーブルを管理する CAcceleratorTable クラス」を書いてみた。
//----------------------------------------------------------------------------
// AcceleratorTable.h : アクセラレータテーブルの管理を行う
//----------------------------------------------------------------------------
#ifndef AcceleratorTableH
#define AcceleratorTableH
#include <windows.h>
//----------------------------------------------------------------------------
// アクセラレータテーブルの管理を行うクラス
//----------------------------------------------------------------------------
class CAcceleratorTable
{
public: // 関数
CAcceleratorTable() : m_hAccel(0) { }
virtual ~CAcceleratorTable() { Destroy(); }
virtual BOOL Add(BYTE fVirt, WORD key, WORD cmd)
{
// ハンドルが存在しない場合は新たに作成する
if(!m_hAccel)
{
ACCEL accel[1];
ZeroMemory(&accel, sizeof(ACCEL));
accel[0].fVirt = fVirt;
accel[0].key = key;
accel[0].cmd = cmd;
Create(accel, 1);
}
else
{
int nAccel = GetNum();
ACCEL * pAccel = new ACCEL[nAccel + 1];
Copy(pAccel, nAccel);
pAccel[nAccel].fVirt = fVirt;
pAccel[nAccel].key = key;
pAccel[nAccel].cmd = cmd;
Create(pAccel, nAccel + 1);
delete [] pAccel;
}
return m_hAccel ? TRUE : FALSE;
}
virtual int Copy(LPACCEL lpAccelDst, int cAccelEntries)
{
if(!m_hAccel) return 0;
return CopyAcceleratorTable(m_hAccel, lpAccelDst, cAccelEntries);
}
virtual BOOL Create(LPACCEL pAccel, int nAccel)
{
Destroy();
m_hAccel = CreateAcceleratorTable(pAccel, nAccel);
return m_hAccel ? TRUE : FALSE;
}
virtual BOOL Delete(BYTE fVirt, WORD key, WORD cmd)
{
if(!m_hAccel) return FALSE;
int nAccel = GetNum();
ACCEL * pAccel = new ACCEL[nAccel];
Copy(pAccel, nAccel);
BOOL bFound = FALSE; // 同じエントリが見つかったかどうか
for(int i = 0; i < nAccel; i++)
{
if(pAccel[i].fVirt == fVirt && pAccel[i].key == key && pAccel[i].cmd == cmd)
bFound = TRUE;
if(bFound) // 見つかったら、1つずつ前に移動
{
pAccel[i].fVirt = pAccel[i + 1].fVirt;
pAccel[i].key = pAccel[i + 1].key;
pAccel[i].cmd = pAccel[i + 1].cmd;
}
}
if(bFound) Create(pAccel, nAccel - 1);
delete [] pAccel;
return m_hAccel ? TRUE : FALSE;
}
virtual BOOL Destroy()
{
BOOL bSuccess = FALSE;
if(m_hAccel)
{
bSuccess = DestroyAcceleratorTable(m_hAccel);
m_hAccel = 0;
}
return bSuccess;
}
// エントリ数を得る
virtual int GetNum() { return Copy(NULL, 0); }
private: // メンバ変数
HACCEL m_hAccel;
public: // メンバ変数の取得
operator HACCEL() const { return m_hAccel; }
};
//----------------------------------------------------------------------------
#endif
これで、動的にアクセラレータテーブルを管理する事ができる。
次。「(2)キーボードショートカットの選択にホットキーコントロールの使用を考えている為、CHotKeyCtrl クラス」を作る。まあ、今までのコントロールと同じイメージ。
//----------------------------------------------------------------------------
// HotKeyCtrl.h : ホットキーコントロールの管理を行う
//----------------------------------------------------------------------------
#ifndef HotKeyCtrlH
#define HotKeyCtrlH
#include <commctrl.h>
#include "Wnd.h"
//----------------------------------------------------------------------------
// ホットキーコントロールの管理を行うクラス
//----------------------------------------------------------------------------
class CHotKeyCtrl : public CWnd
{
public: // 関数
CHotKeyCtrl() { }
virtual ~CHotKeyCtrl() { }
virtual BOOL Create(HWND hParentWnd)
{
Destroy();
INITCOMMONCONTROLSEX ic;
ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
ic.dwICC = ICC_HOTKEY_CLASS;
InitCommonControlsEx(&ic);
m_hWnd = CreateWindow(HOTKEY_CLASS, "", WS_CHILD, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hParentWnd, 0, GetModuleHandle(NULL), NULL);
if(!m_hWnd) return FALSE;
SetProc();
return TRUE;
}
};
//----------------------------------------------------------------------------
#endif
実際に使うホットキーコントロールは、こいつを継承して作る。
次。「(3)キーカスタマイズ用ウィンドウを管理する CKeySettingWnd_MainWnd クラス」を作る。
キーカスタマイズ用ウィンドウには、ショートカット羅列用のリストビューと追加ボタン、変更ボタン、デフォルトの復元ボタン、OKボタン、キャンセルボタンがある。追加ボタン、変更ボタンを押すと、ショートカット入力用の別ウィンドウが表示される。ショートカット入力用ウィンドウには、コマンド選択用コンボボックス、ホットキーコントロール、OKボタン、キャンセルボタンがある。
コマンド名については、キーカスタマイズ用ウィンドウが管理する。GetCommandName関数を持っており、ID→コマンド名、コマンド名→IDの相互変換が可能。
聞々ハヤえもん 2.00 α 1 開発版 を公開しました。WAVE, MP3, Ogg Vorbis の読み込み、再生速度・再生周波数・音程の変更が可能です。Windows XP, Windows Me での動作は確認しましたが、他の環境での動作はテスト環境がないのでわかりません。動作報告は歓迎します。
まだα版なので、足りない機能や動きのおかしな部分などがいくつか残っています。とりあえず、機能が Version 1 系統に追いつくまでは、α版・β版として出していきます。安定版を出すまでに修正していく予定の箇所は以下のとおり。
- スライダを動かしてからファイルを読み込むと、再生速度などの変更が適用されていないのを修正する。
- 巻き戻し・早送り機能をつける。
- コンパイラが大量の警告を吐いているので、それを修正する。
- プレイリスト部分をつくる。
- コントロールバー・プレイリストの表示・非表示を切り替えられるようにする。
- ファイルの保存ができるようにする。
- ウィンドウ上へのファイルのドロップに対応する。
- アイコンへのファイルのドロップに対応する。
- メニューとツールバー、再生時間設定用スライダと再生速度表示用ラベルとの間に区切り線をつける。
- そのほか、Version 1 系統にある機能は一通りつける。
というわけで、ここらでいったんオーディオプレイヤ開発記は終了したいと思います。
それでは、bass.dll を使った音声ファイルの再生を管理する CBass クラスを作ってみます。
とりあえず、追加の依存ファイルに bass.lib を追加します。それで、bass.dll を使うアプリケーションでは、bass.h ファイルをインクルードする必要があるんですが、Bass.h という名前を CBass クラスのヘッダファイルに使いたいので、もともとの bass.h ファイルの名前を bassinc.h などに変更しておきます。
それでは、CBass クラスを作成します。例によって、順次、必要な機能だけを追加していきます。
//----------------------------------------------------------------------------
// Bass.h : bass.dll の管理を行う
//----------------------------------------------------------------------------
#ifndef BassH
#define BassH
#include "bassinc.h"
//----------------------------------------------------------------------------
// bass.dll の管理を行うクラス
//----------------------------------------------------------------------------
class CBass
{
public: // 関数
CBass(): m_hStream(0) { }
virtual ~CBass() { BASS_Free(); }
virtual QWORD ChannelGetLength() {
return BASS_ChannelGetLength(m_hStream);
}
virtual QWORD ChannelGetPosition() {
return BASS_ChannelGetPosition(m_hStream);
}
virtual float ChannelGetSecondsLength() {
return BASS_ChannelBytes2Seconds(m_hStream, ChannelGetLength());
}
virtual float ChannelGetSecondsPosition() {
return BASS_ChannelBytes2Seconds(m_hStream, ChannelGetPosition());
}
virtual BOOL ChannelPause() {
return BASS_ChannelPause(m_hStream);
}
virtual BOOL ChannelPlay() {
return BASS_ChannelPlay(m_hStream, FALSE);
}
virtual BOOL Init(HWND hWnd) {
return BASS_Init(-1, 44100, 0, hWnd, NULL);
}
virtual BOOL Pause() {
return BASS_Pause();
}
virtual BOOL Start() {
return BASS_Start();
}
virtual BOOL StreamCreateFile(LPCTSTR lpFilePath) {
StreamFree();
m_hStream = BASS_StreamCreateFile(FALSE, lpFilePath, 0, 0, 0);
return m_hStream ? TRUE : FALSE;
}
virtual void StreamFree() {
if(m_hStream) BASS_StreamFree(m_hStream), m_hStream = 0;
}
protected: // メンバ変数
HSTREAM m_hStream;
};
//----------------------------------------------------------------------------
#endif
完全にラップを被せてるだけですね。
CMainWnd クラスに CBass m_bass; メンバ変数を追加し、OnCreate 関数内で m_bass.Init(m_hWnd); と初期化するようにしたら、次は ファイル → 開く メニューが選択された場合に「ファイルを開く」ダイアログを表示し、指定された音声ファイルを再生するようにします。
メニューが選ばれると CMenu_MainWnd クラスの OnCommand 関数が呼ばれるので、そこから OnOpenFileMenuSelected 関数を呼ぶようにします。
//----------------------------------------------------------------------------
// メニューが選択された
//----------------------------------------------------------------------------
void CMenu_MainWnd::OnCommand(int id, HWND/* hwndCtl*/, UINT/* codeNotify*/)
{
switch(id)
{
case ID_OPENFILE:
OnOpenFileMenuSelected();
break;
case ID_XXXX:
OnXxxxMenuSelected();
break;
}
}
//----------------------------------------------------------------------------
// ファイル → 開くメニューが選択された
//----------------------------------------------------------------------------
void CMenu_MainWnd::OnOpenFileMenuSelected()
{
m_rMainWnd.OpenFile();
}
//----------------------------------------------------------------------------
CMainWnd::OpenFile 関数の方も追加しておきます。
//----------------------------------------------------------------------------
// ファイルを開く
//----------------------------------------------------------------------------
void CMainWnd::OpenFile()
{
TCHAR filePath[MAX_PATH] = "";
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hInstance = GetModuleHandle(NULL);
ofn.hwndOwner = m_hWnd;
ofn.lpstrFilter = TEXT("再生可能な音声ファイル(*.wav;*.mp3;*.ogg)\0*.wav;*.mp3;*.ogg\0すべてのファイル (*.*)\0*.*\0\0");
ofn.lpstrFile = filePath;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_FILEMUSTEXIST;
if(GetOpenFileName(&ofn))
{
m_bass.StreamCreateFile(filePath);
m_bass.ChannelPlay();
m_timeLabel.SetTime((int)m_bass.ChannelGetSecondsPosition(), (int)m_bass.ChannelGetSecondsLength());
m_timeSlider.SetTime((LONG)m_bass.ChannelGetPosition(), (LONG)m_bass.ChannelGetLength());
}
}
//----------------------------------------------------------------------------
これで、指定された音声ファイルが再生されるようになりました。ここまでのソースコードはこちら ( Hayaemon20060226_src.lzh ) 。
次は、ファイルの再生位置にあわせて、再生位置表示用ラベルやスライダを変更するようにします。これは、0.5 秒間隔のタイマーでやることにします。まず、CWnd クラスに SetTimer, KillTimer, OnTimer 関数を追加し、WndProc から OnTimer に分岐するようにします。
virtual void KillTimer(UINT_PTR nIDEvent) {
::KillTimer(m_hWnd, nIDEvent);
}
virtual void SetTimer(UINT_PTR nIDEvent, UINT nElapse) {
::SetTimer(m_hWnd, nIDEvent, nElapse, NULL);
}
virtual void OnTimer(UINT id) {
WindowProc(WM_TIMER, (WPARAM)id, 0);
}
// CWnd::WndProc 関数内
case WM_TIMER:
pWnd->OnTimer((UINT)wParam);
return 0;
それで、CWnd::OnTimer 関数をオーバーライドした CMainWnd::OnTimer 関数を追加し、ファイルが開かれた時点で、SetTimer(1, 500); とするようにしておきます。
virtual void OnTimer(UINT id) {
m_timeLabel.SetTime(m_bass.ChannelGetSecondsPosition(), m_bass.ChannelGetSecondsLength());
m_timeSlider.SetThumbPos(m_bass.ChannelGetPosition());
CFrameWnd::OnTimer(id);
}
これで、ファイルの再生位置にあわせて、再生位置表示用ラベルやスライダが変更されるようになりました。ここまでのソースコードはこちら ( Hayaemon20060226_2_src.lzh ) 。
続いて、ツールバーの「ファイルを開く」ボタンが押された場合にも、「ファイルを開く」ダイアログを出すようにします。まず、CMainWnd::OnCommand 関数からメニューやツールバーの OnCommand 関数を呼び出すようにします。
virtual void OnCommand(int id, HWND hwndCtl, UINT codeNotify) {
if(!hwndCtl)
m_menu.OnCommand(id, hwndCtl, codeNotify);
else if(hwndCtl == (HWND)m_toolBar)
m_toolBar.OnCommand(id, hwndCtl, codeNotify);
CFrameWnd::OnCommand(id, hwndCtl, codeNotify);
}
CToolBar_MainWnd クラスに OnCommand, OnOpenFileButtonSelected 関数を追加しておきます。
//----------------------------------------------------------------------------
// ファイルを開くボタンが選択された
//----------------------------------------------------------------------------
void CToolBar_MainWnd::OnOpenFileButtonSelected()
{
m_rMainWnd.OpenFile();
}
//----------------------------------------------------------------------------
// ボタンが選択された
//----------------------------------------------------------------------------
void CToolBar_MainWnd::OnCommand(int id, HWND/* hwndCtl*/, UINT/* codeNotify*/)
{
switch(id)
{
case ID_OPENFILE:
OnOpenFileButtonSelected();
break;
}
}
//----------------------------------------------------------------------------
これで、ツールバーの「ファイルを開く」ボタンが押された場合にも、「ファイルを開く」ダイアログが出るようになりました。ここまでのソースコードはこちら ( Hayaemon20060226_3_src.lzh ) 。
bass.dll について色々と調べていたところ、タイムストレッチとピッチシフトを実現するアドオンが見つかりました。えーとこれを使えば、再生速度の変換も音程の変換もできて、WAVE も MP3 も Ogg Vorbis も再生できて……、と。うん、音まわりはほとんどやることがないんじゃないですか。
とりあえず、「安定して使えれば」、「音質が良ければ」という条件がつくので、bass.dll を使ったファイルの再生ができ、再生速度・再生周波数・音程がスライダで変更できるようになったら、アルファ版として出してみようかと思います。
本当に bass.dll を使うとなると、内部仕様を大きく変更することになるので、たぶん聞々ハヤえもん Version 2 系統として出していくことになると思います。もしそうなった場合、現行の Version 1 系統はあれで完成ということになります。うん、なんていうか、おめでとう。もしそうなったらだけど。