予定の変更と削除をできるようにする。〜スケジュール管理ソフトをS!アプリで作ってみよう(その32)

今までは、予定の作成と参照しかできなかった。

プログラムの機能を考える時は、データを元に考えるとよい。そして、データについて、以下の機能があるかどうかを忘れずに確認する。

  • 作成(Create)機能
  • 閲覧(Read)機能
  • 更新(Update)機能
  • 削除(Delete)機能
上記は、それぞれの頭文字をとって、CRUDと言われる。これらについて、検討できていないソフトは、ほぼ確実にバグあると言っていい。(たまに、「更新するには、削除して、作成しなおしてください。」と手抜きを誤魔化している事もあるが。)

今度は、予定の変更と削除をできるようにする。(ただし、携帯電話に保存しているものだけだ。Googleカレンダーに登録している分は、まだできない。)

まず、MainCanvasクラスを修正して、選択のボタンが押された場合に、その日の予定一覧のリストの画面を呼び出すようにする。


package jp.dip.ettem.scheduler;

import java.util.Calendar;
import javax.microedition.lcdui.*;

/**
* メインの画面
* カレンダー、予定を表示する。
* (ToDoも表示する予定)
*/
public class MainCanvas extends Canvas {
private Display display;
private Calendar now; // 今日の日付
private Calendar dateToDisplay; // 選択された日付
private MonthlyCalendar calendar; // 月単位のカレンダー
private EventView eventView; // 予定表示部

/**
* メインの画面
* @param display 表示するDisplay。
*/
public MainCanvas(Display display) {
this.display = display;
now = Calendar.getInstance();
dateToDisplay = Calendar.getInstance();
dateToDisplay.setTime(now.getTime());

// カレンダーを上部に表示する。
calendar = new MonthlyCalendar(this, 0, 0, getWidth(), 0, dateToDisplay);

// 予定を下部に表示する。
eventView = new EventView(this, 0, calendar.height + 1, getWidth(), 0, dateToDisplay);
}

protected void paint(Graphics graphics) {
calendar.paint(graphics);
eventView.paint(graphics);
}

/**
* メインの画面でのキーイベントを処理する。
* @param keyCode 押されたキーのkeyCode。
*/
protected void keyPressed(int keyCode) {
int action = getGameAction(keyCode);

if (keyCode == KEY_STAR
|| keyCode == KEY_POUND) {
// KEY_START、KEY_POUNDはGameActionが割当てられていた。
// カレンダーの表示画面単位での移動。
dateToDisplay.setTime(calendar.getMovedDateByKeyCode(keyCode).getTime());
} else if (action == FIRE){
// 予定の一覧画面へ移動する。
EventList eventList = new EventList(display, this, dateToDisplay.getTime());
display.setCurrent(eventList);
} else {
// 選択日の移動
dateToDisplay.setTime(calendar.getMovedDateByAction(action).getTime());
}

calendar.setDate(dateToDisplay);
eventView.setDate(dateToDisplay);
repaint(); // paintも呼んでいる。
}
}

その日の予定の一覧を表示するEventListクラスを作る。リストの一番最初を選択すると新しい予定を作成する画面になる。他の項目を選択すると、その予定を修正できる。削除ボタンを押すと、その予定を削除する。


package jp.dip.ettem.scheduler;

import java.util.Date;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.List;

public class EventList extends List implements CommandListener {
private Display display;
private Displayable callerDisplayable; // 呼び出し元

private Command deleteCommand;
private Command cancelCommand;

private Event[] events;
private Date date;

public EventList(Display display, Displayable callerDisplayable, Date date) {
super("Events", IMPLICIT);

this.display = display;
this.callerDisplayable = callerDisplayable;
this.date = date;

// 予定の選択コマンドを登録
selectCommand = new Command("Select", Command.ITEM, 1);
addCommand(selectCommand);

// 予定の削除コマンドを登録
deleteCommand = new Command("Delete", Command.ITEM, 1);
addCommand(deleteCommand);

// キャンセルコマンドを登録
cancelCommand = new Command("Cancel", Command.CANCEL, 1);
addCommand(cancelCommand);

setCommandListener(this);

append("New Event", null);

events = Events.getEvents(date);
for (int i = 0; i < events.length; i++) {
append(events[i].what, null);
}
}

public void commandAction(Command command, Displayable displayable) {
int selectedIndex = ((List) displayable).getSelectedIndex();

// Selectボタンが押されたら予定を編集
if (command == List.SELECT_COMMAND) {
EventForm eventForm;
// 予定の入力画面へ移動する。
if (selectedIndex == 0) {
eventForm = new EventForm(display, callerDisplayable, Events.getNewEvent(date));
} else {
eventForm = new EventForm(display, callerDisplayable, events[selectedIndex - 1]);
}
display.setCurrent(eventForm);
} else if (command == deleteCommand) {
if (selectedIndex != 0) {
Events.deleteEvent(events[selectedIndex - 1]);
}
display.setCurrent(callerDisplayable);
} else if (command == cancelCommand) {
display.setCurrent(callerDisplayable);
}
}
}

Eventsクラスを修正して、予定のレコードを削除できるようにする。


package jp.dip.ettem.scheduler;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date;

import javax.microedition.rms.InvalidRecordIDException;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotOpenException;

/**
* 予定をRecordStoreクラスとやり取りします。
*/
public class Events {
static private RecordStore scheduleRecordStore;

/**
* 予定を保存するRecordStoreを設定します。
* @param scheduleRecordStore
*/
static void setRecordStore(RecordStore scheduleRecordStore) {
Events.scheduleRecordStore = scheduleRecordStore;
}

/**
* 新しい予定を返します。
* @return 予定
*/
static Event getNewEvent() {
return new Event();
}

/**
* 開始、終了の予定時刻が現在の日時の予定を返します。
* @param date
* @return 予定
*/
static Event getNewEvent(Date date) {
return new Event(date);
}

/**
* 予定を保存します。
* @param event
*/
static void setEvent(Event event) {
try {
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
DataOutputStream dOut = new DataOutputStream(bOut);

dOut.writeLong(event.startTime.getTime());
dOut.writeLong(event.endTime.getTime());

addStringField(event.what, dOut);
addStringField(event.where, dOut);
addStringField(event.discription, dOut);

byte record[] = bOut.toByteArray();
if (event.recordID == Event.newRecordID) {
scheduleRecordStore.addRecord(record, 0, record.length);
} else {
scheduleRecordStore.setRecord(event.recordID, record, 0, record.length);
}

dOut.close();
bOut.close();
} catch (RecordStoreException e) {

} catch (IOException e) {
// あり得ないはずだが...
}
}

/**
* 保存されている予定を返します。
* @param recordID レコードのID
* @return 予定
*/
static Event getEvent(int recordID) {
Event event = new Event();

try {
ByteArrayInputStream bIn = new ByteArrayInputStream(scheduleRecordStore.getRecord(recordID));
DataInputStream dIn = new DataInputStream(bIn);

event.recordID = recordID;
event.startTime = new Date(dIn.readLong());
event.endTime = new Date(dIn.readLong());

event.what = getStringField(dIn);
event.where = getStringField(dIn);
event.discription = getStringField(dIn);

dIn.close();
bIn.close();
} catch (InvalidRecordIDException e) {

} catch (RecordStoreException e) {

} catch (IOException e) {
// あり得ないはずだが...
}

return event;
}

/**
* 指定された日の予定の配列を返します。
* @param date 予定を取得したい日
* @return 予定の配列
*/
static Event[] getEvents(final Date date) {
Event[] events = new Event[0];

try {
RecordEnumeration recEnum = scheduleRecordStore.enumerateRecords(
new DateRecFilter(date), new DateComparator(), false);

int numRecords = recEnum.numRecords();
events = new Event[numRecords];

for (int i = 0; i < numRecords; i++) {
events[i] = Events.getEvent(recEnum.nextRecordId());
}
} catch (RecordStoreNotOpenException e) {

} catch (InvalidRecordIDException e) {

}

return events;
}


static void deleteEvent(Event event) {
try {
scheduleRecordStore.deleteRecord(event.recordID);
} catch (RecordStoreException e) {

}
}

/**
* 予定のレコードに文字列のフィールドを追加します。
* @param str 文字列
* @param dOut レコードのストリーム
*/
private static void addStringField(String str, DataOutputStream dOut) {
try {
byte bStr[] = str.getBytes("UTF-8");
dOut.writeInt(bStr.length);
dOut.write(bStr, 0, bStr.length);
} catch (UnsupportedEncodingException e) {

} catch (IOException e) {

}
}

/**
* 予定のレコードから文字列のフィールドを取得します。
* @param dIn レコードのストリーム
* @return
*/
private static String getStringField(DataInputStream dIn){
String field = new String();

try {
int fieldLen = dIn.readInt();
byte bField[] = new byte[fieldLen];
dIn.read(bField, 0, fieldLen);
field = new String(bField, "UTF-8");
} catch (IOException e) {

}

return field;
}
}