Chào các bạn,
Hôm nay, Fox xin phép được trình bày cách xây dựng một Rating Windows Control nền dotNET 2.0 .
Đã có code và demo cho bản RC1 (theo bản quyền GPL v3) các bạn tải về xem nhé [IMG]images/smilies/smile.png[/IMG]
I. GIỚI THIỆU
Trong quá trình xây dựng phần mềm quản lý cho nhóm, đến module có tên "Chưng cầu dân ý", Fox muốn mọi thành viên trong nhóm cho điểm ý kiến đang xem xét. Nếu thể hiện bằng cách hiển thí String lên Label thì xoàng quá [IMG]images/smilies/biggrin.png[/IMG] (Bọn trong nhóm dám nói ý kiến cũa Fox là "xoàng"). Lòng tự trọng khiến Fox lao vào Google, CodeProject nghiên cứu và tạo control này cho bọn chúng tâm phục khẩu phục [IMG]images/smilies/smile.png[/IMG])
II. Ý TƯỞNG
Qua nhiều lần lênh đênh trên mạng, Fox đã có được mục tiêu thực hiện. Có một số ý kiến dùng đồ họa trong C# vẽ hình, số khác thì dùng PictureBox... Fox sẽ gộp cả 2 ý kiến trên để thực hiện.
1. Công cụ
a. IDE dành cho C#: VS 2005, SharpDevelop 2.2 (nếu dùng SharpDevelop 3.0 thì phải cài dotNET 3.5).
b. Phần mềm thiết kế ảnh (Ở đây Fox dùng PhotoShop CS2 để vẽ, XnView để chỉnh sửa ảnh).
2. Lên kế hoạch
a. Kiểu dáng: hình sao và hình trái tim (do đây là 2 hình phổ biến).
b. Mức điểm tối thiểu là 0, tối đa là 5; cho phép hiển thị điểm + 0.5.
c. Mỗi kiểu dáng cần tối thiểu 3 hình (hình nền chỉ có viền tim hoặc viền sao, hình một nửa quả tim hoặc một nửa ông sao, hình full tim hoặc full sao).
d. Rating Control của Fox cho phép người dùng rê chuột trên nó, rê đến đâu thì hiển thị hình đến đó. Ví dụ rê 2 ông sao thì vẽ 2 ông sao full và 3 sao rỗng; rê đến 2 ông sao và một nửa ông sao thứ 3 thì vẽ 2 ông sao full, 1 nửa ông sao và 2 ông sao rỗng...
e. Muốn chọn số điểm thích hợp thì chỉ việc click chuột ngay hình sao tại điểm rê cuối cùng. Ví dụ: theo ví dụ trên thì click chuột ở 2 ông sao diểm là 2.0; click chuột ngay một nữa ông sao thứ 3 thì số điểm là 2.5 ...
f. Cho phép chỉ một lần hoặc nhiều lần rate.
g. Khi một user khác rate tiếp nhưng chưa click chọn điểm thì khi rê chuột khỏi control thì vẫn hiện thị số sao và số điểm đã được rate lần trước; nếu chọn điểm luôn thì hiển thị số điểm hiện tại và số điểm trước đó.
III. CODING
1. Vẽ
Fox sẽ up mẫu sao và tim minh họa cho các bạn, nếu các bạn không thích có thể tự thiết kế mẫu riêng cho mình [IMG]images/smilies/smile.png[/IMG] . Ở đây fox dùng size 32x32 cho mục đích hướng dẫn, Fox khuyên nếu các bạn muốn áp dụng thực tế nên dùng cỡ 18x18 hoặc 24x24 là tốt nhất.
2. Gõ
a. Vào IDE new một project Class Library; trong project này new một UserControl (đặt tên tùy thích, ở đây Fox dùng tên RatingBar), sau đó switch UserControl này sang màn hình Coding (vì bạn không cần làm bất cứ thứ gì trên màn hình Designer cả).
b. Khai báo một enum (bên ngoài Class) định dạng cho kiểu dáng của Control (theo mục a phần II.2 Lên kế hoạch)
Mã:
public enum IconStyle{ Star, // hình sao Heart // hình trái tim}
c. Trong class RatingBar fox khai báo 2 biến hằng (theo mục b phần II.2 Lên kế hoạch)
Mã:
public class RatingBar : UserControl{... const byte ICON_DISTANCE = 1; // Khoãng cách giữa các hình const byte MAX_ICONS = 5; // số điểm tối đa là 5. ...}
d. Khai báo các biến toàn cục, delegate, event
Để thực hiện việc ghi nhận điểm, ta viết một Class sau
Mã:
//Thực hiện mục g phần II.2 Lên kế hoạchpublic class RatingEventArgs : EventArgs { #region - field(s) - private float mCurrentRate; private float mLastRate; #endregion public RatingEventArgs(float current, float last) { mCurrentRate = current; mLastRate = last; } public float CurrentRate { get { return mCurrentRate; } set { mCurrentRate = value; } } public float LastRate { get { return mLastRate; } set { mLastRate = value; } } } // Đăng ký sự kiện khi user bình chọn trên control. public delegate void OnRatingChangedEventHandler(object sender, RatingEventArgs e); [DefaultEvent("RatingValueChanged")]public class RatingBar : UserControl{... // Sự kiện khi user bình chọn trên control public event OnRatingChangedEventHandler RatingValueChanged; float mRating = 0; // biến quản lý số điểm dự tính bình chọn. float mRatingHolder = 0; // biến quản lý số điểm đã bình chọn. bool mReadOnly = false; // cho phép xem không cho phép rate. bool mRatingOnce = false; // Chỉ cho phép rate 1 lần. bool mIsRated = false; // Kiểm tra đã rate hay chưa (dùng kèm với mRatingOnce). Image mEmptyIcon; // Quản lý hình viền tim hoặc viền sao. Image mHalfIcon; // Quản lý hình nửa tim hoặc nửa sao. Image mFullIcon; // Quản lý hình full tim hoặc full sao. IconStyle mIconStyle; // Kiểu dáng của control. PictureBox mContainerBar; // Canvas để vẽ ...}
e. Contructor
Mã:
public RatingBar(){ SetStyle(ControlStyles.SupportsTransparentBackColor, true); // cho phép có nền trong suốt. mContainerBar = new PictureBox(); mContainerBar.BackgroundImageLayout = ImageLayout.Center; mContainerBar.MouseMove += new MouseEventHandler(ContainerBarOnMouseMoving); // Đăng kí sự kiện MouseMove (mục d phần II.2 Lên kế hoạch) mContainerBar.MouseLeave += new EventHandler(ContainerBarOnMouseLeaving); // Đăng kí sự kiện MouseLeave (mục g phần II.2 Lên kế hoạch) mContainerBar.MouseClick += new MouseEventHandler(ContainerBarOnMouseClicking); // Đăng kí sự kiện MouseClick (mục e phần II.2 Lên kế hoạch) mContainerBar.Cursor = Cursors.Hand; mContainerBar.Dock = DockStyle.Fill; this.BackColor = Color.Transparent; this.Controls.Add(mContainerBar); this.UpdateIcons(); this.UpdateControlSize(); // draws empty icons first... this.ReDraw();}
f. Bỏ hình vào file Resource
Các bạn mở file Resource của Rating Control (.resx) chọn Add Resource -> Add Exist File... và chọn các hình mẫu Fox gửi lên.
g. Coding những events đã đăng kí cho PictureBox
Mã:
void ContainerBarOnMouseMoving(object sender, MouseEventArgs e) { if (mReadOnly || (mRatingOnce && mIsRated)) return; Bitmap bitmap = new Bitmap(mContainerBar.Width, mContainerBar.Height); Graphics graphic = Graphics.FromImage(bitmap); int i = 0; // mốc hoành độ bắt đầu vẽ. float tempRating = 0; byte tempIconsCounter = MAX_ICONS; // temporary variable to hold the iconsCount value, because we're decreasing it on each loop for (int a = 0; a < MAX_ICONS; a++) // lần lượt vẽ 5 hình sao rỗng hoặc tim rỗng. { if (e.X > i && e.X <= i + mEmptyIcon.Width / 2) // nếu rê đến những điểm một nửa sao hoặc nửa tim { graphic.DrawImage(mHalfIcon, i, 0, mEmptyIcon.Width, mEmptyIcon.Height); i += ICON_DISTANCE + mEmptyIcon.Width; tempRating += 0.5f; } else if (e.X > i) // rê trọn hình { graphic.DrawImage(mFullIcon, i, 0, mEmptyIcon.Width, mEmptyIcon.Height); i += ICON_DISTANCE + mEmptyIcon.Width; tempRating += 1.0f; } else // rê qua 5 hình break; tempIconsCounter--; //giảm số hình rỗng } mRatingHolder = tempRating; this.DrawEmptyIcons(graphic, i, tempIconsCounter); // vẽ hình rỗng còn lại. graphic.Dispose(); mContainerBar.BackgroundImage = bitmap; // hiển thị hình kết quả lên PictureBox } void ContainerBarOnMouseLeaving(object sender, EventArgs e) { if (mReadOnly || (mRatingOnce && mIsRated)) return; Bitmap bitmap = new Bitmap(mContainerBar.Width, mContainerBar.Height); Graphics graphic = Graphics.FromImage(bitmap); int i = 0; byte tempIconsCounter = MAX_ICONS; float tempRating = mRating; while (tempRating > 0) { if (tempRating > 0.5f) { graphic.DrawImage(mFullIcon, i, 0, mEmptyIcon.Width, mEmptyIcon.Height); // Draw icons with the dimension of iconEmpty, so that they do not look odd i += ICON_DISTANCE + mEmptyIcon.Width; } else if (tempRating == 0.5f) { graphic.DrawImage(mHalfIcon, i, 0, mEmptyIcon.Width, mEmptyIcon.Height); // Draw icons with the dimension of iconEmpty, so that they do not look odd i += ICON_DISTANCE + mEmptyIcon.Width; } else break; tempIconsCounter--; tempRating--; } this.DrawEmptyIcons(graphic, i, tempIconsCounter); graphic.Dispose(); mContainerBar.BackgroundImage = bitmap; } void ContainerBarOnMouseClicking(object sender, MouseEventArgs e) { float tempOldRate = mRating; mRating = mRatingHolder; mIsRated = true; if (mRatingOnce) mContainerBar.Cursor = Cursors.Default; if (RatingValueChanged != null && tempOldRate != mRating) RatingValueChanged(this, new RatingEventArgs(mRating, tempOldRate)); }
h. Những method hỗ trợ
Mã:
void DrawEmptyIcons(Graphics g, int x, int count){ for (byte a = 0; a < count; a++) { g.DrawImage(mEmptyIcon, x, 0, mEmptyIcon.Width, mEmptyIcon.Height); x += ICON_DISTANCE + mEmptyIcon.Width; }} // Lấy kích cở cho controlvoid UpdateControlSize(){ int width = mEmptyIcon.Width * MAX_ICONS + (ICON_DISTANCE * (MAX_ICONS - 1)); int height = mEmptyIcon.Height; this.Size = new Size(width, height); this.MaximumSize = new Size(width, height); // Không cho mở rộng control khi thiết kế *một bug chưa fix hoàn chỉnh được } // Khi chọn kiểu cho controlvoid UpdateIcons(){ if (mIconStyle == IconStyle.Star) { mEmptyIcon = RatingBar_Resources.EmptyStar; // mFullIcon = RatingBar_Resources.FullStar; // lấy trong những file ảnh trong resource. mHalfIcon = RatingBar_Resources.HalfStar; // } else { mEmptyIcon = RatingBar_Resources.EmptyHeart; mFullIcon = RatingBar_Resources.FullHeart; mHalfIcon = RatingBar_Resources.HalfHeart; } this.ReDraw(); // Vẽ lại nền} /// <summary>/// Redraws empty-icons when choose another icon-style./// </summary>private void ReDraw(){ Bitmap bitmap = new Bitmap(mContainerBar.Width, mContainerBar.Height); Graphics graphic = Graphics.FromImage(bitmap); this.DrawEmptyIcons(graphic, 0, MAX_ICONS); graphic.Dispose(); mContainerBar.BackgroundImage = bitmap;}
i. Properties dùng cho thiết kế
Mã:
public Image EmptyIcon { get { return mEmptyIcon; } } public Image HalfIcon { get { return mHalfIcon; } } public Image FullIcon { get { return mFullIcon; } } [DefaultValue(false)] public bool ReadOnly { get { return mReadOnly; } set { mReadOnly = value; if (value) mContainerBar.Cursor = Cursors.Default; else mContainerBar.Cursor = Cursors.Hand; } } [DefaultValue(false)] public bool RatingOnce { get { return mRatingOnce; } set { mRatingOnce = value; if (!value) mContainerBar.Cursor = Cursors.Hand; /* Set hand cursor, if false is set from true*/ } } [Browsable(false)] public float Rate { get { return mRating; } } public Color BarBackColor { get { return mContainerBar.BackColor; } set { mContainerBar.BackColor = value; } } [DefaultValue(typeof(IconStyle), "star")] public IconStyle IconStyle // Chọn kiểu dáng cho control Tim hoặc Sao { get { return mIconStyle; } set { mIconStyle = value; UpdateIcons(); } }
Nhấn build project thôi [IMG]images/smilies/biggrin.png[/IMG]
IV. ÁP DỤNG
Tạo một project Windows Form khác trong cùng Solution và Add Reference đến Project chứa Control của chúng ta. Tạo một Form sau đó vào màn hình thiết kế sẽ có một control tên RatingBar cho các bạn kéo thả vào form [IMG]images/smilies/biggrin.png[/IMG]
[old] Đáng tiết là Fox chưa thể up code cho các bạn (vài lý do tế nhị), chỉ có hướng dẫn và file demo và code cho Form thôi
Đã up code và demo cho RC1 (theo bản quyền GPL v3).
Nếu có gì thắc mắc hoặc phát hiện bug các bạn cứ trao đổi với Fox nhé.
V. PHIÊN BẢN
1. Ngày 20/03/2009:
a. Fixing:
_ Khi thay đổi property IconStyle từ Heart->Star và ngược lại sẽ vẽ lại lên PictureBox.
_ Up code Beta 2
b. Known bug:
_ Chưa fix cho property AutoSize giống kiểu Label - dùng tạm property MaximumSize.
_ Some unfix minor bugs...
c. Things to do:
_ Mặc định kiểu sao và kiểu tim (không cho user thay đổi resource)
_ Sự kiện mặc định cho Control là RatingValueChanged (double-click vào Control sẽ tự động đăng ký)
_ Fix minor bugs...
2. Ngày 22/03/2009:
a. Fixing:
_ Mặc định kiểu sao và kiểu tim (không cho user thay đổi resource)
_ Sự kiện mặc định cho Control là RatingValueChanged (double-click vào Control sẽ tự động đăng ký)
_ Hỗ trợ màu nền trong suốt (Transparent).
_ Fix minor bugs...
_ Up to RC1 version (theo bản quyền GPL v3)
c. Things to do:
_ Thay đổi size của IconStyle cho control (16x16, 18x18, 24x24)
_ Cho phép add control trên ToolStrip, MenuStrip hoặc StatusStrip trong quá trình design form.
_ Fix minor bugs...
_ Up to version 1.0.0 final.
View more random threads:
Ngoại trừ một số ít trường hợp rãnh mũi - má và “râu rồng silicon” xuất hiện sớm, có khi từ tuổi thanh niên do cơ địa, còn lại, đại đa số do căn do lão hoá đã gây nên ba diễn biến: Giảm mô xương gò...
Cách thẩm mỹ má ở tuổi trung niên...