Yupoo (現已轉型) 早期的「日曆相簿」以直觀的月曆介面展示每日照片,點擊日期即看當日相簿。本文示範如何使用 ASP.NET `Calendar` 伺服器控制項,搭配 `DayRender` 事件動態標記有照片的日期、`SelectionChanged` 事件處理點擊彈出詳情,達成高度客製化的日曆相簿體驗。
<asp:Calendar ID="Calendar1" runat="server"
OnDayRender="Calendar1_DayRender"
OnSelectionChanged="Calendar1_SelectionChanged"
OnVisibleMonthChanged="Calendar1_VisibleMonthChanged"
ShowGridLines="True"
NextPrevFormat="FullMonth"
TitleFormat="MonthYear"
FirstDayOfWeek="Sunday">
<DayStyle CssClass="cal-day" />
<TodayDayStyle CssClass="cal-today" BackColor="#FFFFCC" />
<SelectedDayStyle CssClass="cal-selected" BackColor="#FF6600" ForeColor="White" />
<OtherMonthDayStyle CssClass="cal-other-month" ForeColor="#CCCCCC" />
<SelectorStyle CssClass="cal-selector" />
<WeekendDayStyle CssClass="cal-weekend" />
</asp:Calendar>
<!-- 詳情顯示區 (初始隱藏) -->
<asp:Panel ID="pnlDetail" runat="server" Visible="false" CssClass="detail-panel">
<asp:Image ID="imgPhoto" runat="server" />
<asp:Label ID="lblDate" runat="server" CssClass="detail-date" />
<asp:Label ID="lblDescription" runat="server" CssClass="detail-desc" />
<asp:Button ID="btnClose" Text="關閉" OnClick="btnClose_Click" runat="server" />
</asp:Panel>
public partial class CalendarAlbum : System.Web.UI.Page
{
// 模擬資料來源:日期 -> 相片資訊
private Dictionary _photos = new Dictionary();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
LoadSampleData();
Calendar1.VisibleDate = DateTime.Today;
}
}
private void LoadSampleData()
{
// 實務從資料庫載入:SELECT PhotoDate, ImageUrl, Title, Description FROM Photos
_photos[new DateTime(2024, 1, 5)] = new PhotoInfo
{ ImageUrl = "~/Photos/20240105.jpg", Title = "新年首登", Description = "陽明山日出" };
_photos[new DateTime(2024, 1, 12)] = new PhotoInfo
{ ImageUrl = "~/Photos/20240112.jpg", Title = "週末騎行", Description = "河濱自行車道" };
_photos[new DateTime(2024, 1, 28)] = new PhotoInfo
{ ImageUrl = "~/Photos/20240128.jpg", Title = "年夜飯", Description = "全家福" };
}
// 關鍵:動態標記有照片的日期
protected void Calendar1_DayRender(object sender, DayRenderEventArgs e)
{
if (_photos.ContainsKey(e.Day.Date))
{
var photo = _photos[e.Day.Date];
// 加入圖片縮圖
var img = new Image
{
ImageUrl = photo.ImageUrl,
Width = Unit.Pixel(40),
Height = Unit.Pixel(30),
ToolTip = photo.Title,
CssClass = "photo-thumb"
};
e.Cell.Controls.Add(img);
// 標記 CSS 類別供樣式區分
e.Cell.CssClass += " has-photo";
}
}
// 使用者點擊日期
protected void Calendar1_SelectionChanged(object sender, EventArgs e)
{
DateTime selected = Calendar1.SelectedDate;
if (_photos.ContainsKey(selected))
{
var photo = _photos[selected];
imgPhoto.ImageUrl = photo.ImageUrl;
lblDate.Text = selected.ToString("yyyy年MM月dd日");
lblDescription.Text = $"{photo.Title}:{photo.Description}";
pnlDetail.Visible = true;
}
else
{
pnlDetail.Visible = false; // 無照片不顯示詳情
}
}
// 切換月份時重新載入該月資料 (大量資料時必要)
protected void Calendar1_VisibleMonthChanged(object sender, MonthChangedEventArgs e)
{
// 實務:LoadPhotosForMonth(e.NewDate.Year, e.NewDate.Month);
}
protected void btnClose_Click(object sender, EventArgs e)
{
pnlDetail.Visible = false;
Calendar1.SelectedDates.Clear(); // 取消選中狀態
}
}
public class PhotoInfo
{
public string ImageUrl { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
.cal-day { height: 60px; width: 60px; vertical-align: top; padding: 2px; }
.cal-day.has-photo { background-color: #E8F5E9; border: 2px solid #4CAF50; }
.photo-thumb { display: block; margin: 2px auto; border-radius: 4px; }
.cal-today { background-color: #FFF3E0 !important; border: 2px solid #FF9800; }
.cal-selected { background-color: #FF6600 !important; }
.cal-other-month { background-color: #F5F5F5; }
.detail-panel {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.2);
z-index: 1000; max-width: 400px; text-align: center;
}
.detail-panel img { max-width: 100%; height: auto; margin-bottom: 10px; }
| 挑戰 | 解決策略 |
|---|---|
| 大量照片 (萬筆) 載入緩慢 | `VisibleMonthChanged` 只載入當月資料;使用 `OutputCache` 快取月份資料 |
| 行動裝置觸控體驗 | 加大日期單元格、支援滑動切換月份 (jQuery Mobile / Hammer.js) |
| 多張照片同一天 | 縮圖改用 Carousel、或顯示數量徽章 `e.Cell.Controls.Add(new Label { Text = $"+{count}" })` |
| 無障礙存取 | 加入 `aria-label`、鍵盤導航支援、高對比度模式 |
`Calendar` 控制項雖屬 WebForms 經典元件,但其 `DayRender` 事件模式——**資料驅動 UI 渲染**——是現代前端框架 (Vue `v-for`、React `map`、Blazor `@foreach`) 共同的核心範式。掌握這個控制項的客製化技巧,不僅能維護舊系統,更能深化對「宣告式 UI 渲染」的本質理解。