こんにちは、あんこ先生です。
前回で投稿画面ができて、アプリの中だけで一周できるようになりました。
今回は最後のSNSらしい機能、コメントを付けていきます。
投稿をタップすると詳細画面が開いて、そこにコメントが並ぶ形ですね。
いいねのときに使った「関連するリストを投稿IDで探す」考え方が、そのまま活きてきますよ。
まだ読んでいない人は、先に投稿画面の回まで進めておいてくださいね。
まずは開くボタンから

タイムラインのいいねボタンの右に、コメントボタンを置きましょう。
モダンボタンを追加して、名前を btnComment にします。
| プロパティ | 設定する式 |
|---|---|
| Icon | “Chat” |
| Text | Text(ThisItem.CommentCount) |
| Layout | ButtonLayout.IconBefore |
| Appearance | ButtonAppearance.Transparent |
| Color | RGBA(120, 120, 120, 1) |
| Height | 32 |
| Width | 90 |
| X | 186 |
| Y | btnLike.Y |
いいねボタンと同じ高さに並べたいので、Y は btnLike.Y をそのまま参照しています。
こう書いておけば、上の部品が伸びていいねボタンが下がったときも、コメントボタンが置いていかれません。
どの投稿を開いたか、を渡す
押したときの処理です。
//コメントボタン btnComment の OnSelect(詳細画面をひらく) //どの投稿を開いたのかを変数に入れてから移動する Set(glbSelectedPost, ThisItem); Navigate(scrPostDetail, ScreenTransition.None)
画面を移動すると、当然ながら「ギャラリーの何行目を押したか」という情報は失われます。
そこで、押された行そのものを glbSelectedPost に入れてから移動しています。
移動先では、この変数を見れば投稿の中身が分かる、というわけですね。
グローバル変数の使いどころは、グローバル変数でコントロールを変化させる方法にもまとめてあります。
詳細画面を作る
新しい画面を追加して、名前を scrPostDetail にします。
上から順に、戻るボタン、投稿の中身、コメント一覧、入力欄という並びです。
開いたときにコメントを読み込む
画面の OnVisible に、こちらを入れます。
//画面 scrPostDetail の OnVisible(この投稿へのコメントを新しい順に読み込む)
//送信中フラグを倒してから、PostID で絞って取得する
UpdateContext({ ctxCommentSending: false });
ClearCollect(
colComments,
SortByColumns(
Filter(Comments, PostID = glbSelectedPost.PostID),
"Created",
SortOrder.Descending
)
)Comments リストを PostID で絞って、新しい順に並べています。
いいねのときに Likes を絞ったのと、まったく同じ考え方ですね。
投稿とコメントは PostIDという合言葉でつながっている、とイメージすると分かりやすいと思います。
投稿の中身を再表示する
画面の上のほうに、いま開いている投稿を表示します。
コンテナ conPostBody を置いて、その中にアバターと本文を並べましょう。
参照するのは、さっき渡した glbSelectedPost です。
| コントロール | プロパティ | 設定する式 |
|---|---|---|
| avaPost | Image | glbSelectedPost.登録者.Picture |
| lblDetailAuthor | Text | glbSelectedPost.Author.DisplayName & ” → ” & glbSelectedPost.ToUser.DisplayName |
| lblDetailBody | Text | glbSelectedPost.Body |
| lblDetailTags | Text | Concat(glbSelectedPost.Tags, Value & ” “) |
タイムラインで書いた式の ThisItem を、glbSelectedPost に置き換えただけです。
アバターの画像だけ 登録者 と日本語で書くところも、タイムラインと同じですね。
コメントを並べる
その下に、コメントの一覧を置きます。
ギャラリーを追加して、名前を galComments に。
レイアウトは、タイムラインと同じ「縦(可変の高さ)」を選んでください。
コメントも長さがバラバラですから、行の高さが伸び縮みしてくれないと困りますよね。
| プロパティ | 設定する式 |
|---|---|
| Items | colComments |
| Width | Parent.Width |
| TemplatePadding | 0 |
中に置く部品は、タイムラインの縮小版です。
| コントロール | プロパティ | 設定する式 |
|---|---|---|
| avaComment | Image | ThisItem.登録者.Picture |
| avaComment | Width | 36 |
| lblCommentAuthor | Text | コメント者名と相対時刻(下で解説) |
| lblCommentAuthor | AutoHeight | true |
| lblCommentBody | Text | ThisItem.CommentBody |
| lblCommentBody | AutoHeight | true |
| lblCommentBody | Y | lblCommentAuthor.Y + lblCommentAuthor.Height + 2 |
ここでも AutoHeight とY座標の連鎖を使っています。
第2回で覚えたやり方が、そのまま別の画面でも通用するわけですね。
コメント者と時刻の表示も、タイムラインで作った式を流用できます。
//ラベル lblCommentAuthor の Text(コメントした人と、経過時間)
//タイムラインで作った相対時刻の式を、そのまま持ってきている
ThisItem.Author.DisplayName
& " ・ "
& With(
{ mins: DateDiff(ThisItem.Created, Now(), TimeUnit.Minutes) },
Switch(
true,
mins < 1, "たった今",
mins < 60, Text(mins) & "分前",
mins < 1440, Text(RoundDown(mins / 60, 0)) & "時間前",
mins < 10080, Text(RoundDown(mins / 1440, 0)) & "日前",
Text(ThisItem.Created, "yyyy/mm/dd")
)
)同じ式を2か所に書くのが気になる人は、名前付き数式にまとめる手もありますよ。
コメントを送る
画面のいちばん下に、入力欄と送信ボタンを置きます。
| コントロール | プロパティ | 設定する式 |
|---|---|---|
| txtComment | Placeholder | “コメントを入力” |
| txtComment | Height | 44 |
| txtComment | Width | Parent.Width – 40 – 56 |
| txtComment | Y | Parent.Height – 64 – 44 – 10 |
| btnSendComment | Icon | “Send” |
| btnSendComment | Layout | ButtonLayout.IconOnly |
| btnSendComment | DisplayMode | If(ctxCommentSending || IsBlank(txtComment.Text), DisplayMode.Disabled, DisplayMode.Edit) |
| btnSendComment | X | Parent.Width – 56 |
入力が空のあいだは送信ボタンを押せないようにしています。
送信の中身
OnSelect に入れる式です。
//送信ボタン btnSendComment の OnSelect(コメントを登録して、その場で一覧に反映する)
//1. Commentsに登録 2. 投稿のコメント数を1増やす 3. 一覧を取り直す 4. 入力欄を空にする
UpdateContext({ ctxCommentSending: true });
Patch(
Comments,
Defaults(Comments),
{
PostID: glbSelectedPost.PostID,
CommentID: GUID(),
CommentBody: txtComment.Text
}
);
With(
{ cur: LookUp(Posts, PostID = glbSelectedPost.PostID) },
Patch(Posts, cur, { CommentCount: cur.CommentCount + 1 })
);
ClearCollect(
colComments,
SortByColumns(
Filter(Comments, PostID = glbSelectedPost.PostID),
"Created",
SortOrder.Descending
)
);
Reset(txtComment);
UpdateContext({ ctxCommentSending: false })コメント本体の登録は、PostID ・ CommentID ・ CommentBody の3つだけ。
コメントした人と日時は、いつものように 登録者 と 登録日時 が自動で入ります。
そのあと ClearCollect で一覧を取り直しているので、送信した瞬間に自分のコメントが並びます。
最後の Reset は、入力欄を空に戻すためのものですね。
ここでハマりました

さて、この記事でいちばんお伝えしたいのが、次のところです。
わたしは最初、コメント数を増やす部分をこう書いていました。
//一見よさそうだけど、うまくいかない書き方
Patch(
Posts,
LookUp(Posts, PostID = glbSelectedPost.PostID),
{ CommentCount: glbSelectedPost.CommentCount + 1 }
)開いている投稿のコメント数に1を足す。素直な書き方に見えますよね。
ところが実際に試すと、何回コメントしても、コメント数が1のままだったんです。
原因は glbSelectedPost の性格でした。
この変数は、画面を開いた瞬間にコピーした写しなんですね。
コメントを登録してSharePoint側の数が増えても、変数の中身は開いたときのまま。ずっと0です。
だから「0 + 1」を何度も計算していた、というわけでした。
その都度、読み直す
直し方は、いまの値を毎回取りに行くことです。
//投稿のコメント数を1増やす(押すたびに、いまの値を読み直してから足す)
//With でいったん受け取っておくと、同じ問い合わせを2回書かずに済む
With(
{ cur: LookUp(Posts, PostID = glbSelectedPost.PostID) },
Patch(Posts, cur, { CommentCount: cur.CommentCount + 1 })
)LookUp で投稿を探し直してから、そのコメント数に1を足しています。
With を使って cur という名前で受け取っているのは、同じ LookUp を2回書かないためです。
同じ値を何度も使うときに便利な書き方なので、With関数でコードを読みやすく省力化する方法もどうぞ。
あちらはこう書いていました。
Patch(Posts, ThisItem, { LikeCount: ThisItem.LikeCount + 1 })ポイントは ThisItem です。
これはギャラリーの「その行そのもの」を指していて、写しではありません。だから押した瞬間の最新の値が入っていて、何度押しても正しく増減してくれたわけです。同じ「1を足す」でも、足す相手が写しなのか本物なのかで結果が変わる。わたしはここで小一時間悩みました。同じ罠にハマる人が減るとうれしいです。
LookUp の使い方は、LookUp関数の正しい使い方にくわしくまとめてあります。
戻るボタンを付けておく
最後に、タイムラインへ帰れるようにしておきましょう。
画面の左上にモダンボタンを置いて、名前を btnBack にします。
| プロパティ | 設定する式 |
|---|---|
| Icon | “Back” |
| Layout | ButtonLayout.IconOnly |
| Appearance | ButtonAppearance.Transparent |
| OnSelect | Navigate(scrTimeline, ScreenTransition.None) |
スマホだと端末の戻る操作もありますが、画面の中にも置いておくと親切ですね。
まとめ
今回はここまでにしましょう。
・別の画面へ「どれを開いたか」を渡すときは、押された行を変数に入れてから移動する。
・コメントは PostID で絞って取ってくる。いいねのときとまったく同じ考え方。
・数を増やすときは、足す相手が写しなのか本物なのかを確かめる。変数は開いたときのまま止まっている。
・登録したあとに ClearCollect で取り直すと、その場で一覧に反映される。
これで投稿・タイムライン・いいね・コメントと、SNSらしい部分がひととおり揃いました。
次の一手として、次回はマイページを作ります。
自分が送ったありがとうの数、受け取った数、最近もらったメッセージを並べる画面ですね。
第1回で「カウント専用のリストは作らない」と決めた理由が、ここで分かるはずですよ。
それでは、次の記事でお会いしましょう。


















・投稿の詳細画面と、コメントの一覧
・コメントの入力欄と送信
・投稿ごとのコメント数の更新