2015年11月23日 星期一
避免children control 的事件觸發parent的事件
今天寫了一個滑鼠移入與點擊時觸發的選單,裡面放了幾個按鈕,發現按鈕的mouseenter事件,會連帶觸發容器的mouseenter事件
找了一下發現可以用以下方式來避免
$(document).ready(function(){
$(".header").click(function(){
$(this).children(".children").toggle();
});
$(".header a").click(function(e) {
e.stopPropagation();
});
});
2015年9月1日 星期二
影像處理-提取像素
這幾天寫到一些簡單的影像處理,本來是用Bitmap.GetPixel
卻發現寫出來的程式佔用很多的cpu,還超慢
上網查到一篇有用的網誌,為了避免該網誌掛掉,我這邊也貼一份
我參考的網誌: http://mermerism.blogspot.tw/2014/04/c-bitmap.html
出處:http://blog.csdn.net/ma_jiang/article/details/7725458
一.Bitmap類別
Bitmap對象封裝了GDI+中的一個位元圖,此位元圖由圖形圖像及其屬性的像素數據組成.
見維基百科:http://zh.wikipedia.org/wiki/BMP
即對每一個單一像素在一個位元圖上的處理。
因此Bitmap是用於處理由像素數據定義的圖像的對象.該類的主要方法和屬性如下:
1. GetPixel方法和SetPixel方法:和設置一個圖像的指定像素的顏色.
2. PixelFormat屬性:返回圖像的像素格式.
3. Palette屬性:獲取和設置圖像所使用的顏色調色板.
4. Height Width屬性:返回圖像的高度和寬度.
5. LockBits方法和UnlockBits方法:分別鎖定和解鎖系統內存中的位圖像素.
在基於像素點的圖像處理方法中使用LockBits和UnlockBits是一個很好的方式,
這兩種方法可以使我們指定像素的範圍來控制位圖的任意一部分,
從而消除了通過循環對位圖的像素逐個進行處理,每用LockBits之後都應該調用一次UnlockBits.
二.BitmapData類別
BitmapData對象指定了位元圖的屬性,指定點陣圖影像的屬性 (Attribute)。 BitmapData 類別是由 Bitmap 類別的 LockBits 和 UnlockBits 方法所使用。 無法被繼承。
1. Height屬性:被鎖定位圖的高度.
2. Width屬性:被鎖定位圖的高度.
3. PixelFormat屬性:數據的實際像素格式.
4. Scan0屬性:被鎖定數組的首字節地址,如果整個圖像被鎖定,則是圖像的第一個字節地址.
5. Stride屬性:步幅,也稱為掃描寬度.
如上圖所示,數組的長度並不一定等於圖像像素數組的長度,還有一部分未用區域,
這涉及到位圖的本身結構,系統要保證每行的字節數必須為4的倍數.
否則上圖右側灰色區域的未使用空間(offset)就是一排中非4的倍數中的畸零地
三.Graphics類別
Graphics對像是GDI+的關鍵所在,許多對像都是由Graphics類表示的,該類別定義了繪製和填充圖形對象的方法和屬性,一個應用程序只要需要進行繪製或著色,它就必須使用Graphics對象.
就是在一張圖上要做些甚麼繪製的動作時,就是使用這個類別
四.Image類別
這個類提供了位元圖和位元文件操作的函數.
Image類被聲明為abstract,也就是說Image類不能實例化對象,而只能做為一個基本類
1.FromFile方法:從指定的檔案建立 Image 物件。
它根據輸入的文件名產生一個Image對象,它有兩種函數形式:
public static Image FromFile(string filename);
//從指定的檔案建立 Image 物件。
public static Image FromFile(string filename, bool useEmbeddedColorManagement);
//使用指定之檔案中的內嵌色彩管理資訊,從該檔案建立 Image
2.FromHBitmap方法:從 Windows 控制代碼建立 Bitmap。
它從一個windows句柄處創建一個bitmap對象,它也包括兩種函數形式:
public static bitmap fromhbitmap(intptr hbitmap);
//從 GDI 點陣圖控制代碼建立 Bitmap。
public static bitmap fromhbitmap(intptr hbitmap, intptr hpalette);
//從 GDI 點陣圖控制代碼和 GDI 調色盤控制代碼建立 Bitmap
3. FromStream方法:從指定的資料流建立 Image。
從一個數據流中創建一個image對象,它包含三種函數形式:
public static image fromstream(stream stream);
//從指定的資料流建立 Image。
public static image fromstream(stream stream, bool useembeddedcolormanagement);
//選擇性地使用指定之資料流中的內嵌色彩管理資訊,從該資料流建立 Image。
fromstream(stream stream, bool useembeddedcolormanagement, bool validateimagedata);
//選擇性地使用内嵌色彩管理資訊並驗證影像資料,從指定的資料流建立 Image。
有了上面了理解,在看下面的部分
一. 開檔、存檔、讀檔、寫檔
開檔、存檔、讀檔、寫檔 分別有其代表之意義。
開檔:是打開檔案顯示在銀幕上
存檔:是將做好的檔案儲存在電腦的資料夾
讀檔:是將檔案中的資料讀成Byte或Bit的檔案,讓電腦看得懂
寫檔:是將檔案讀出來之後,使用者才做更改、修正
所以在檔案處理的順序來說,應該是要
開檔-->讀檔-->寫檔-->存檔
private Bitmap srcBitmap = null;//原始的Bitmap
private Bitmap showBitmap = null;//顯示用的Bitmap
//打開文件
private void menuFileOpen_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = @"Bitmap文件(*.bmp)|*.bmp|Jpeg文件(*.jpg)|*.jpg|所有合適文件
(*.bmp,*.jpg)|*.bmp;*.jpg ";
ofd.FilterIndex = 3;
ofd.RestoreDirectory = true;
if (DialogResult.OK == openFileDialog.ShowDialog())
{
srcBitmap = (Bitmap)Bitmap.FromFile(ofd.FileName, false);
showBitmap = srcBitmap;
this.AutoScroll = true;
this.AutoScrollMinSize =newSize((int)(showBitmap.Width),
(int)(showBitmap.Height));
this.Invalidate();
}
}
//保存圖像文件
private void menuFileSave_Click(object sender, EventArgs e)
{
if (showBitmap != null)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter =@"Bitmap文件(*.bmp)|*.bmp|Jpeg文件(*.jpg)|*.jpg|所有合適文件
(*.bmp,*.jpg)|*.bmp;*.jpg";
sfd.FilterIndex = 3;
sfd.RestoreDirectory = true;
if (DialogResult.OK == sfd.ShowDialog())
{
ImageFormat format = ImageFormat.Jpeg;
switch (Path.GetExtension(sfd.FileName).ToLower())
{
case".jpg":
format = ImageFormat.Jpeg;
break;
case".bmp":
format = ImageFormat.Bmp;
break;
default:
MessageBox.Show(this,"Unsupported image format was specified","Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
try
{
showBitmap.Save(sfd.FileName,format );
}
catch (Exception)
{
MessageBox.Show(this, "Failed writing image file", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
c#中將bitmap或者image保存為清晰的gif
在c#中默認可以講bitmap保存為gif等格式,但是這種保存方法保存的gif會嚴重失真,正常情況下的代碼:
System.Drawing.Bitmap b = new System.Drawing.Bitmap(“c://original_image.gif“);
System.Drawing.Image thmbnail = b.GetThumbnailImage(100,75,null,new IntPtr());
thmbnail.Save(“c://thumnail.gif“, System.Drawing.Imaging.ImageFormat.Gif);
一個批量處理圖片的軟件,包括各種處理方式,處理效果,但是在保存為gif的時候出現了問題,在網上查了很久也沒有發現一個可用的改善gif圖片質量的方法,找到了一個解決辦法,保存出來的gif容量大減,但是效果基本符合常規這中方法就是就是“Octree“ 算法。
“Octree“ 算法允許我們插入自己的算法來量子化我們的圖像。
一個好的“顏色量子化”算法應該考慮在兩個像素顆粒之間填充與這兩個像素顏色相近的過渡顏色,提供更多可視顏色空間。
Morgan Skinner提供了很好的“Octree“ 算法代碼,大家可以下載參考使用。
使用OctreeQuantizer很方便:
System.Drawing.Bitmap b = new System.Drawing.Bitmap(“c://original_image.gif“);
System.Drawing.Image thmbnail = b.GetThumbnailImage(100,75,null,new IntPtr());
OctreeQuantizer quantizer = new OctreeQuantizer ( 255 , 8 ) ;
using ( Bitmap quantized = quantizer.Quantize ( thmbnail ) )
{
quantized.Save(“c://thumnail.gif“, System.Drawing.Imaging.ImageFormat.Gif);
}
OctreeQuantizer grayquantizer = new GrayscaleQuantizer ( ) ;
using ( Bitmap quantized = grayquantizer.Quantize ( thmbnail ) )
{
quantized.Save(“c://thumnail.gif“, System.Drawing.Imaging.ImageFormat.Gif);
}
你可以點擊這裡下載類的文件(項目文件 ),根據我的試用,只需要兩個類文件(OctreeQuantizer.cs,Quantizer.cs)即可運行,將這兩個類文件的namespace改成
你項目的名稱就行,還有,需要在不安全編譯的方式下編譯,右擊項目名稱,在生成選項卡里選擇"允許不安全代碼"即可
//窗口重繪,在窗體上顯示圖像,重載Paint
privatevoid frmMain_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (showBitmap != null)
{
Graphics g = e.Graphics;
g.DrawImage(showBitmap, newRectangle(this.AutoScrollPosition.X, this.AutoScrollPosition.Y ,
(int)(showBitmap.Width), (int)(showBitmap.Height)));
}
}
//灰度化
privatevoid menu2Gray_Click(object sender, EventArgs e)
{
if (showBitmap == null) return;
showBitmap = RGB2Gray(showBitmap);//下面都以RGB2Gray為例
this.Invalidate();
}
二. 提取像素法(超級無敵慢)
即用C#中的getPixel和setPixel 來做取得和設定像素。
這種方法簡單易懂,但相當耗時,完全不可取.
public staticBitmap RGB2Gray(Bitmap srcBitmap)
{
Color srcColor;
int wide = srcBitmap.Width;
int height = srcBitmap.Height;
for (int y = 0; y < height; y++)
for (int x = 0; x < wide; x++)
{
//獲取像素的RGB顏色值
srcColor = srcBitmap.GetPixel(x, y);
byte temp = (byte)(srcColor.R * .299 + srcColor.G * .587 + srcColor.B * .114);
//設置像素的RGB顏色值
srcBitmap.SetPixel(x, y, Color.FromArgb(temp, temp, temp));
}
return srcBitmap ;
}
三. 內存法(速度較快)
這是比較常用的方法,即http://softwarebydefault.com/2013/05/18/image-median-filter/所用之法
大概的步驟如下:
先建立一個rect,這是用來放圖的框框
public static Bitmap RGB2Gray(Bitmap srcBitmap)
{
int wide = srcBitmap.Width;
int height = srcBitmap.Height;
Rectangle rect = newRectangle(0, 0, wide, height);
//將srcBitmap鎖定到系統內的記憶體的某個區塊中,並將這個結果交給BitmapData類別的srcBimap
BitmapData srcBmData = srcBitmap.LockBits(rect,ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
//將CreateGrayscaleImage灰階影像,並將這個結果交給Bitmap類別的dstBimap
Bitmap dstBitmap = CreateGrayscaleImage(wide, height);//這個函數在後面有定義
//將dstBitmap鎖定到系統內的記憶體的某個區塊中,並將這個結果交給BitmapData類別的dstBimap
BitmapData dstBmData = dstBitmap.LockBits(rect,ImageLockMode.ReadWrite,
PixelFormat.Format8bppIndexed);
//位元圖中第一個像素數據的地址。它也可以看成是位圖中的第一個掃描行
//目的是設兩個起始旗標srcPtr、dstPtr,為srcBmData、dstBmData的掃描行的開始位置
System.IntPtr srcPtr = srcBmData.Scan0;
System.IntPtr dstPtr = dstBmData.Scan0;
//將Bitmap對象的訊息存放到byte中
int src_bytes = srcBmData.Stride * height;
byte[] srcValues = new byte[src_bytes];
int dst_bytes = dstBmData.Stride * height;
byte[] dstValues = new byte[dst_bytes];
//複製GRB信息到byte中
System.Runtime.InteropServices.Marshal.Copy(srcPtr, srcValues, 0, src_bytes);
System.Runtime.InteropServices.Marshal.Copy(dstPtr, dstValues, 0, dst_bytes);
//根據Y=0.299*R+0.114*G+0.587B,Y為亮度
for (int i = 0; i < height; i++)
for (int j = 0; j < wide; j++)
{
//只處理每行中圖像像素數據,捨棄未用空間
//注意位圖結構中RGB按BGR的順序存儲
int k = 3 * j;
byte temp = (byte)
(srcValues[i * srcBmData.Stride + k + 2] * .299 +
srcValues[i * srcBmData.Stride + k + 1] * .587+
srcValues[i * srcBmData.Stride + k] * .114);
dstValues[i * dstBmData.Stride + j] = temp;
}
System.Runtime.InteropServices.Marshal.Copy(dstValues, 0, dstPtr, dst_bytes);
//解鎖位圖
srcBitmap.UnlockBits(srcBmData);
dstBitmap.UnlockBits(dstBmData);
return dstBitmap;
}
四 指標法(速度最快)
C/C++的習慣,不是C#的特點,但是效率奇高
public static Bitmap RGB2Gray(Bitmap srcBitmap)
{
int wide = srcBitmap.Width;
int height = srcBitmap.Height ;
Rectangle rect = newRectangle(0, 0, wide, height);
//將srcBitmap鎖定到系統內的記憶體的某個區塊中,並將這個結果交給BitmapData類別的srcBimap
BitmapData srcBmData = srcBitmap.LockBits(rect,ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
//將CreateGrayscaleImage灰階影像,並將這個結果交給Bitmap類別的dstBimap
Bitmap dstBitmap = CreateGrayscaleImage(wide, height);
//將dstBitmap鎖定到系統內的記憶體的某個區塊中,並將這個結果交給BitmapData類別的dstBimap
BitmapData dstBmData = dstBitmap.LockBits(rect,ImageLockMode.ReadWrite,
PixelFormat.Format8bppIndexed);
//位元圖中第一個像素數據的地址。它也可以看成是位圖中的第一個掃描行
//目的是設兩個起始旗標srcPtr、dstPtr,為srcBmData、dstBmData的掃描行的開始位置
System.IntPtr srcScan = srcBmData.Scan0;
System.IntPtr dstScan = dstBmData.Scan0;
Unsafe //啟動不安全代碼
{
byte* srcP = (byte*)(void*) srcScan;
byte* dstP = (byte*)(void*) dstScan;
int srcOffset = srcBmData.Stride - wide * 3;
int dstOffset = dstBmData.Stride - wide ;
byte red, green, blue;
for (int y = 0; y < height; y++)
{
for (int x = 0; x
2015年8月23日 星期日
[常用]將所有dll檔封裝進exe檔(WPF)
一般我們寫程式的時候都會用到許多dll
但是把程式交出去的時候如果有一堆dll會很難管理,也不是很好看
這時候就需要把dll封裝進程式執行檔裡面
1.修改Project.csproj
2.右鍵點選參考底下的dll,把複製到本機這個屬性改為False,該dll就不會一起複製到程式執行的資料夾內。
但此動作會使得程式參考不到該dll檔
3.將dll檔加進Resources內,建置方式選擇內嵌資源,就跟一般放影像檔、音效檔一樣,會把dll封裝進exe執行檔內。
但程式呼叫到該dll時不會自動找到Resources內的dll檔,必須自行對應
4.新建一支Program.cs檔,取代App.xaml.cs成為專案起始檔(右鍵點專案可以設定起始檔)
完成以上步驟後,再建置程式,就會發現dll檔都不見了,而exe檔明顯變肥
但是把程式交出去的時候如果有一堆dll會很難管理,也不是很好看
這時候就需要把dll封裝進程式執行檔裡面
1.修改Project.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
參考自http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application | |
在最底部增加這幾行 | |
<Target Name="AfterResolveReferences"> | |
<ItemGroup> | |
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'"> | |
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName> | |
</EmbeddedResource> | |
</ItemGroup> | |
</Target> |
2.右鍵點選參考底下的dll,把複製到本機這個屬性改為False,該dll就不會一起複製到程式執行的資料夾內。
但此動作會使得程式參考不到該dll檔
3.將dll檔加進Resources內,建置方式選擇內嵌資源,就跟一般放影像檔、音效檔一樣,會把dll封裝進exe執行檔內。
但程式呼叫到該dll時不會自動找到Resources內的dll檔,必須自行對應
4.新建一支Program.cs檔,取代App.xaml.cs成為專案起始檔(右鍵點專案可以設定起始檔)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text; | |
namespace MyProject | |
{ | |
public class Program | |
{ | |
[STAThreadAttribute] | |
public static void Main() | |
{ | |
var assemblies = new Dictionary<string, Assembly>(); | |
var executingAssembly = Assembly.GetExecutingAssembly(); | |
var resources = executingAssembly.GetManifestResourceNames().Where(n => n.EndsWith(".dll")); | |
//設定dll的參考路徑以供程式執行時對應 | |
foreach (string resource in resources) | |
{ | |
using (var stream = executingAssembly.GetManifestResourceStream(resource)) | |
{ | |
if (stream == null) | |
continue; | |
var bytes = new byte[stream.Length]; | |
stream.Read(bytes, 0, bytes.Length); | |
try | |
{ | |
assemblies.Add(resource, Assembly.Load(bytes)); | |
} | |
catch (Exception ex) | |
{ | |
System.Diagnostics.Debug.Print(string.Format("Failed to load: {0}, Exception: {1}", resource, ex.Message)); | |
} | |
} | |
} | |
//當程式參考不到dll時會觸發,將我們對應好的dll位置回傳 | |
AppDomain.CurrentDomain.AssemblyResolve += (s, e) => | |
{ | |
var assemblyName = new AssemblyName(e.Name); | |
var path = string.Format("{0}.dll", assemblyName.Name); | |
//不知道為什麼,自行加在Resources內的dll都會把路徑全部寫進名稱,所以這邊我自己把路徑補上以免對應不到 | |
if (assemblies.ContainsKey(path) || assemblies.ContainsKey("Projectname.Resources."+path)) | |
{ | |
return assemblies["Projectname.Resources." + path]; | |
} | |
return null; | |
}; | |
App.Main(); | |
} | |
} | |
} |
完成以上步驟後,再建置程式,就會發現dll檔都不見了,而exe檔明顯變肥
2015年7月17日 星期五
超實用 強制更新Listview的Itemsource
ICollectionView view = CollectionViewSource.GetDefaultView(DeviceSeries.Instance.dic_device);
view.Refresh();
view.Refresh();
2015年4月22日 星期三
DisplayTemplates & EditorTemplates
在mvc中,我們可以為特定型別定義模板
當在razor語法中,用某些語法呼叫該型別時,會自動對應到Shared資料夾下的模板來顯示
@Html.DisplayFor ->Shared/DisplayTemplates/型別名稱
@Html.EditorFor->Shared/EditorTemplates/型別名稱
例如,當我們在razor中使用@Html.DisplayFor(m=>m.myText),myText是string型別時,會使用Shared/DisplayTemplates/String.cshtml來取代該字串的顯示
以上的模板是針對網站上所有該型別的資料,如果只是想要讓特定幾筆資料使用模板,可以將Shared/DisplayTemplates/下的String.cshtml改名,然後再到model使用的型別上定義要使用的模板
如String.cshtml->MyString.cshtml
model class
[UIHint("MyString")]
public string myText{ get; set;}
當在razor語法中,用某些語法呼叫該型別時,會自動對應到Shared資料夾下的模板來顯示
@Html.DisplayFor ->Shared/DisplayTemplates/型別名稱
@Html.EditorFor->Shared/EditorTemplates/型別名稱
例如,當我們在razor中使用@Html.DisplayFor(m=>m.myText),myText是string型別時,會使用Shared/DisplayTemplates/String.cshtml來取代該字串的顯示
以上的模板是針對網站上所有該型別的資料,如果只是想要讓特定幾筆資料使用模板,可以將Shared/DisplayTemplates/下的String.cshtml改名,然後再到model使用的型別上定義要使用的模板
如String.cshtml->MyString.cshtml
model class
[UIHint("MyString")]
public string myText{ get; set;}
2015年4月20日 星期一
MVC migration command
在Package Manager Console(nuget套件管理員->套件管理器主控台)下輸入
Enable-Migrations - ContextTypeName 開啟該資料表的migration
這個指令會產生 Migrations 資料夾,其中包含 Configuration.cs 檔案
Configuration.cs中的Seed function可以輸入進行migration時要建立的資料
add-migration Initial 重新產生資料表
add-migration 欄位名稱 新建欄位
最後輸入update database即可更新資料表,此時會連帶建立Seed()中定義的資料 add-migration DataAnnotations 可更新資料欄位的定義(格式 長度限制等)
Enable-Migrations - ContextTypeName 開啟該資料表的migration
這個指令會產生 Migrations 資料夾,其中包含 Configuration.cs 檔案
Configuration.cs中的Seed function可以輸入進行migration時要建立的資料
add-migration Initial 重新產生資料表
add-migration 欄位名稱 新建欄位
最後輸入update database即可更新資料表,此時會連帶建立Seed()中定義的資料 add-migration DataAnnotations 可更新資料欄位的定義(格式 長度限制等)
2015年4月19日 星期日
MVC 日期顯示方式(Model中的日期參數)
DateTime在View中使用Model.DateTime來綁定的話,預設會顯示yyyy/MM/dd hh/mm/ss
想要修改顯示方式的話,可以在參數上加上這行
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
2015年3月25日 星期三
同時使用viewport3D與RenderTargetBitmap時發生的問題
最近有個專案,在使用3D模組的同時,還要不斷地對其拍照上傳
於是我使用了RenderTargetBitmap.Render(viewport3D)來對整個3D建模進行拍攝
但是我發現這行指令執行完畢後,viewport3D上正在執行的動畫效果及貼圖全都會消失
疑似是該指令讓viewport3D失去綁定關係
目前解決的方法是在把viewport3D從其父容器上移除並重新加入
2015年3月13日 星期五
Json with Linq
之前本來是用 Newtonsoft.Json 的JsonConverter來解Json字串
但是要把整個物件建起來實在太麻煩啦,雖然相對地比較嚴謹就是了
Newtonsoft.Json.Linq 下的JObject用起來方便多了
Jobject o = JObject.Parse(json_string);
class aaa = (class)o["parameter_name"]
類似javascript在抓物件的方式,省去很多功夫
2015年2月16日 星期一
PerspectiveCamera(3D) in WPF
有幾個參數
Position代表攝影機的位置
LookDirection 代表攝影機在該位置面對的方向
UpDirection 代表攝影機的上方 一般不設定時為(0,1,0) 如果改成(0,-1,0)會發現物件顛倒了
2015年1月4日 星期日
apt-get update失敗-解決方法
修改/etc/resolv.conf文件:
sudo wi /etc/resolv.conf
按s修改內文
nameserver=8.8.8.8
按Esc=> :wq存檔離開
然後再次執行sudo apt-get update
訂閱:
文章 (Atom)