Nine MVP's Blog

24/09/2013

Entity Framework: Partial Update Object

Introduce

เป็นปากเสียงกันได้เลยสำหรับคนใช้ Entity Framework (EF) และ NHibernate (NH) ถึงเรื่องความสามารถต่างๆ
อาจจะเพราะ EF เกิดมาช้าเลยพากันขัดตาไปเสียหมด อาจจะเพราะว่า NH ได้ถูกนำโค้ดจาก Hibernate ที่เป็น JAVA มาเขียนเป็น C# ใหม่ด้วยกระมัง
ดังนั้นจึงมีของให้ใช้เยอะและตรงใจผู้พัฒนาค่อนข้างมาก

มาถึงเหตุให้ต้องเขียนตอนนี้ขึ้นมาเพราะมีคำถามว่า EF มี Merge() method สำหรับการ Update เฉพาะ property ที่เรากำหนดค่าได้หรือไม่

ยกตัวอย่างเช่นว่า มี 


public class User
{
    public int Id { get; set; }     

public string UserName { get; set; }

    public string Password { get; set; }

    public DateTime LastLogin { get; set; }

}

แต่ต้องการแค่อยากจะอัพเดทแค่ Password กับ LastLogin property เท่านั้นแบบนี้ โดยที่ไม่กำหนด property อื่นๆ
และเมื่อส่งไปอัพเดทใน database ต้องไม่ทำให้ property อื่นๆที่ไม่ได้กำหนดค่ากลายเป็น Null แบบตัวอย่างข้างล่างนี้


//pass user object and specify update properties
userDto.Password = “P@ssw0rd”;
userDto.LastLogin = DateTime.Now;db.PartialUpdate(userDto);

db.SaveChanges();

 

คำตอบคือ ไม่มีหรอกเสียใจด้วยครับ

แต่……ช้าก่อน  เราทำได้ครับ EF เก่งพอที่จะให้เราโชว์ฝีเท้าใส่โค้ดเข้าเพิ่มเติมเข้าไปเองได้

..

SOLUTION 1 Passing Property Name

วิธีแรกก็คือ ก็ส่งชื่อ Property ที่ต้องการจะ update เข้าไปเองก็สิ้นเรื่อง แต่ก็ไม่ถึงกับต้องมานั่งพิมพ์ค่า property name ใส่ string variable เข้าไปกันเองหรอกนะ มีวิธีที่ดีกว่านั้น


เริ่มต้นด้วยการติดตั้ง
Entity Framework ลงในโปรเจคก่อน
จากนั้นสร้าง
User class ขึ้นตาม introduce ด้านบน
ต่อมากก็สร้าง UserContext class สำหรับใช้งานตาม code ด้านล่างนี้


public partial class UserContext : DbContext
{
    public UserContext() : base(“name=UserDB”) { }    

public DbSet<User> Users { get; set; }

}

เสร็จแล้วให้เพิ่ม class file อีกอัน สำหรับสร้าง Partial Class เพื่อเพิ่ม method สำหรับทำ PartialUpdate ตาม codeด้านล่าง ซึ่งจะเห็นได้ว่ามีแต่ Method Name และ Signature ตัว code จะเพิ่มให้ใน block หลังๆครับ


public partial class UserContext
{     

public void PartialUpdate<T>(T entity, params Expression<Func<T, object>>[] propsToUpdate) where T : class {}    

private IEnumerable<string> ConfigPartialUpdate<T>(T entity, out DbEntityEntry<T> entry) where T : class { }

private IEnumerable<string> GetPrimaryKeys<T>() where T : class {}

}

จากโค้ดด้านบนจะเห็นได้ว่ามี 3 methods ซึ่งเป็น public 1, private 2  ซึ่งขออธิบายดังนี้

GetPrimaryKeys() Method เราเอาไว้ดึง primary key ทั้งหมดที่มีใน T class ที่เราส่งเข้ามาเพื่อทำการอัพเดท โดยดึง property name ออกมาเพื่อที่จะให้ข้ามการอัพเดทไป เพราะจะทำให้เกิด Error หากสั่งอัพเดทรายการนี้


private IEnumerable<string> GetPrimaryKeys<T>() where T : class
{    

ObjectContext objectthis = ((IObjectContextAdapter)this).ObjectContext;

ObjectSet<T> set = objectthis.CreateObjectSet<T>();

IEnumerable<string> keyNames = set.EntitySet.ElementType.KeyMembers.Select(k => k.Name);

    return keyNames;

}

ConfigPartialUpdate() Method จะมีหน้าที่ในการตรวจสอบ object ที่จะทำการอัพเดทโดยป้องกันการ attach object ซ้ำซ้อนใน DbContext


private IEnumerable<string> ConfigPartialUpdate<T>(T entity, out DbEntityEntry<T> entry) where T : class
{

    //set validate on save 
   

this.Configuration.ValidateOnSaveEnabled = false;


//get primary keys 

var pks = GetPrimaryKeys<T>();


//get object entry

entry = this.Entry(entity);


//if dbthis is already tracking T object then remove it all and attach new object

if (entry.State == EntityState.Detached && this.ChangeTracker.Entries<T>().Any())

{

foreach (DbEntityEntry<T> item in this.ChangeTracker.Entries<T>())

{

this.Entry<T>(item.Entity).State = EntityState.Detached;

}

this.Set<T>().Attach(entity);

}

//if T object is detached 

else if (entry.State == EntityState.Detached)

{

this.Set<T>().Attach(entity);

}

return pks;

}

PartialUpdate() Method เราจะใช้ในการกำหนด property ที่มีการแก้ไขค่าและบอกให้ EF ทำการอัพเดท
โดยใช้ params Expression<Func<T, object>>[] มาช่วยในการรับค่า property ที่ต้องการจะทำการอัพเดท


public void PartialUpdate<T>(T entity, params Expression<Func<T, object>>[] propsToUpdate) where T : class
{   

DbEntityEntry<T> entry;

var pks = ConfigPartialUpdate(entity, out entry);

foreach (var prop in propsToUpdate)

{

//if prop.name is pk go next loop

if (pks.Contains(prop.Name))

continue;

var errors = entry.Property(prop).GetValidationErrors();

if (errors.Count == 0)

{

entry.Property(prop).IsModified = true;

}

else

{

throw new DbEntityValidationException(string.Format({0} Model has errors please check.”, typeof(T).Name), errors.Select(o => new DbEntityValidationResult(entry, errors)));

}

}

}

..

SOLUTION 1 DEMO TEST

มาทดลอง method ที่เราเขียนกันไว้ดูด้วย code ข้างล่างนี้

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DemoPartialUpdate.DAL;
using DemoPartialUpdate.Domain;
namespace DemoPartialUpdate
{

class Program

{

static void Main(string[] args)

{

using (var db = new UserContext())

{

//If first run

if (db.Database.CreateIfNotExists())

{

for (int i = 1; i < 3; i++)

{

User user = new User();

user.Id = i;

user.UserName = “User” + i;

user.Password = “1234”;

user.LastLogin = DateTime.Now;

db.Users.Add(user);

}

db.SaveChanges();

}


var userx = db.Users.AsNoTracking().ToList();

Console.ForegroundColor = ConsoleColor.Yellow;

Console.WriteLine(“Before Update……..”);


foreach (var user in userx)

{

Console.WriteLine(“User Id {0}, UserName {1}, Password {2}, LastLogin {3}, user.Id, user.UserName, user.Password, user.LastLogin.ToString(“G”));

}

Console.WriteLine();


//Simulate DTO object

var userDto = new User();

userDto.Id = 1; //Id is a key it will not update

userDto.Password = “P@ssw0rd”; //Update

userDto.LastLogin = DateTime.Now.AddDays(1); //Update


//pass user object and specify update properties

db.PartialUpdate(userDto, x => x.Password, x => x.LastLogin);

db.SaveChanges();

userx = db.Users.AsNoTracking().ToList();

Console.ForegroundColor = ConsoleColor.Green;

Console.WriteLine(“After Update……..”);

foreach (var user in userx)

{

Console.WriteLine(“User Id {0}, UserName {1}, Password {2}, LastLogin {3}, user.Id, user.UserName, user.Password, user.LastLogin.ToString(“G”));

}

}

}

}

}

จากโค้ดด้านบนเป็นการสั่งให้ Create Database หากไม่มีอยู่
และให้ทำการ
loop 2 เพื่อสร้าง User Object ลงไปใน Database
จากนั้นทำการ simulate ด้วยการสมมติว่ารับค่ามาจากภายนอก โดยกำหนด userDto Object ขึ้นมาและระบุ Id = 1 เพื่อบอกว่าให้อัพเดทค่าไปที่ User.Id =1 ต่อมากำหนดค่า 2 property คือ Password, LastLogin เพื่อต้องการให้อัพเดทลง Database
แล้วเราก็ดึงออกมาแสดงผลหลังทำการ
Insert ลงไปครั้งแรกโดยระบุ AsNoTracking() เพื่อไม่ให้ DbContext เก็บค่า object เอาไว้ติดตามในระบบ

และถึงเวลาทดสอบ เราก็เรียก db.PartialUpdate() โดยส่ง userDto เข้าไปและใช้ lambada x=> กำหนด property ที่ต้องการจะให้ update เข้าไป

ต่อมาก็สั่ง SaveChanges() เพื่อให้ commit รายการลง Database

สุดท้ายก็ดึงค่า ขึ้นมาจาก Database ทั้ง 2 User เพื่อแสดงค่าที่เปลี่ยนแปลงไปของ 2 property ที่สั่ง PartialUpdate ไป

เราไปดูใน Database หลังจากที่รันโค้ดชุดด้านบนนี้ไปแล้ว จะพบว่าเจอ 2 records ใน Users Table และมีการ update ค่าถูกต้องที่ User.Id = 1

ซึ่งเราไม่ได้กำหนดค่าให้ UserName Property หากใช้ Update โดยการ Attach() ของ DbContext ตรงๆ จะทำให้กลายเป็น Null ไปนั่นเอง

..

SOLUTION 2 Implement INotifyPropertyChanged

วิธีนี้จะไปยุ่งยากกับ Entity Object ในระบบทั้งหมด แต่จะทำให้ PartialUpdate method สะอาดไม่รกเต็มไปด้วย Paramter ว่าแล้วก็เริ่มลงมือ

แรกเริ่มเราจะเพิ่ม UpdatePartial() method เข้ามาใน partial class UserContext ใหม่อีก 1 method


public partial class UserContext
{

//////////


public void PartialUpdate<T>(T entity) where T : EntityBase

{


DbEntityEntry<T> entry;

var pks = ConfigPartialUpdate(entity, out entry);


foreach (var prop in entity.PropertyChangedList)

{

if (pks.Contains(prop))

continue;

var errors = entry.Property(prop).GetValidationErrors();


if (errors.Count == 0)

{

entry.Property(prop).IsModified = true;

}

else

{

throw new DbEntityValidationException(string.Format({0} Model has errors please check.”, typeof(T).Name), errors.Select(o => new DbEntityValidationResult(entry, errors)));

}

}

}

///////////////

}

เสร็จแล้วให้ทำการสร้าง NotifyPropertyChangedInvocatorAttribute Class เอาไว้ช่วยให้เราไม่ต้องระบุ property name ในตอนเรียก ตาม Code ด้านล่างนี้

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute {

public NotifyPropertyChangedInvocatorAttribute() }

public NotifyPropertyChangedInvocatorAttribute(string parameterName)

ParameterName = parameterName; }


public string ParameterName { get; private set; }

}

ต่อไปจะสร้าง BaseEntity สำหรับเป็นตัวช่วยให้ Entity Classของเราเก็บค่า Property Name ที่มีการเปลี่ยนแปลงค่าจากการ assign ค่าได้

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace DemoPartialUpdate.Domain
{

public abstract class EntityBase : INotifyPropertyChanged

{


protected EntityBase(){ }


private HashSet<string> _propertyChangeList = new HashSet<string>();


public HashSet<string> PropertyChangedList { get { return _propertyChangeList; } }


public void AddPropertyChanged(string propertyName)

{

if (!_propertyChangeList.Contains(propertyName))

_propertyChangeList.Add(propertyName);

}


public void ClearPropertyChanges()

{

_propertyChangeList.Clear();

}


public event PropertyChangedEventHandler PropertyChanged;

[NotifyPropertyChangedInvocator]

protected virtualvoid OnPropertyChanged([CallerMemberName] string propertyName = null)

{

PropertyChangedEventHandler handler = PropertyChanged;

if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));

AddPropertyChanged(propertyName);

}

}

}

จากนั้นไปเปิด User Class มาแก้ไขให้ได้ตามโค้ดด้านล่างนี้ โดยนำ EntityBase ไป Implement

using System;

using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;

using System.Linq;

using System.Runtime.InteropServices;

using System.Text;

using System.Threading.Tasks;


namespace DemoPartialUpdate.Domain
{

public class User : EntityBase

{


private int _id;

private string _userName;

private string _password;

private DateTime _lastLogin;

public int Id {

get { return _id; }

set { _id = value; }

}

public string UserName {

get { return _userName; }

set {

_userName = value;

OnPropertyChanged();

}

}

public string Password {

get { return _password; }

set {

_password = value;

OnPropertyChanged();

}

}

public DateTime LastLogin {

get { return _lastLogin; }

set {

_lastLogin = value;

OnPropertyChanged();

}

}

}

}

SOLUTION 2 DEMO TEST

ให้เรา copy test class จาก Solution 1แล้วเปลี่ยนเป็น db.PartialUpdate(userDto);

ก่อนรันให้ไปลบ UserDB Database ทิ้งก่อน

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DemoPartialUpdate.DAL;
using DemoPartialUpdate.Domain;
namespace DemoPartialUpdate
{

class Program 

{

static void Main(string[] args)

{

using (var db = new UserContext())

{

//If first run

if (db.Database.CreateIfNotExists())

{

for (int i = 1; i < 3; i++) {

User user = new User();

user.Id = i;

user.UserName = “User” + i;

user.Password = “1234”;

user.LastLogin = DateTime.Now;

db.Users.Add(user);

}

db.SaveChanges();

}


var userx = db.Users.AsNoTracking().ToList();

Console.ForegroundColor = ConsoleColor.Yellow;

Console.WriteLine(“Before Update……..”);


foreach (var user in userx)

{

Console.WriteLine(“User Id {0}, UserName {1}, Password {2}, LastLogin {3}, user.Id, user.UserName, user.Password, user.LastLogin.ToString(“G”));

}

Console.WriteLine();


//Simulate DTO object

var userDto = new User();

userDto.Id = 1; //Id is a key it will not update

userDto.Password = “P@ssw0rd”; //Update

userDto.LastLogin = DateTime.Now.AddDays(1); //Update

//pass user object and specify update properties

db.PartialUpdate(userDto);

db.SaveChanges();

userx = db.Users.AsNoTracking().ToList();

Console.ForegroundColor = ConsoleColor.Green;

Console.WriteLine(“After Update……..”);

foreach (var user in userx)

{

Console.WriteLine(“User Id {0}, UserName {1}, Password {2}, LastLogin {3}, user.Id, user.UserName, user.Password, user.LastLogin.ToString(“G”));

}

}

}

}

}

ซึ่งก็จะได้ผลลัพธ์เหมือนกับ Solution 1

Conclusion

สรุปว่าทั้งสองวิธีมีข้อดีและข้อเสียแตกต่างกัน แต่ได้ผลลัพธ์เดียวกัน

แบบที่ กำหนด property ที่ต้องการด้วยตัวเอง

ข้อดีคือ ใช้งานง่ายเหมาะกับระบบที่มีโมเดลอยู่แล้ว และไม่ต้องการจะไปรื้อโครงสร้างให้วุ่นวาย
ข้อเสียคือ จำเป็นต้องระบุ
property ที่ต้องการให้ Change ค่าเอง ซึ่งก็อาจจะหลงๆลืมๆส่งไปไม่ครบก็ได้ อาจจะทำงานผิดพลาด

แบบที่ ใช้ INotifyPropertyChanged เข้าช่วยในการตรวจสอบการเปลี่ยนแปลง

ข้อดีคือ ไม่ต้องกังวลว่าจะลืมส่ง Property อะไรไปบ้าง จุดเรียกใช้งานก็ใช้ง่าย
ข้อเสียคือ ต้องแก้ไข
entity class อาจจะกระทบงานเก่าที่มีอยู่

หวังว่าตอนนี้จะเป็นประโยชน์ ในการนำไปใช้งาน และช่วยเพิ่มความรู้และเทคนิคต่างๆให้เพื่อนๆกันได้ไม่มากก็น้อย

download source code  

จบครับ

23/09/2013

Object Mapping Library Performance Testing

ตอนนี้ปั่นไวๆ ไม่มีแบบแผนอะไรมาก เนื่องจากมีการ discuss กันในกลุ่ม ASP.NET & MVC Developers Thailand เกี่ยวกับการทำ mapping object
และได้หยิบยกประเด็นเรื่อง
Performance ว่าน่าวิตกแค่ไหนสำหรับการใช้ Library กลุ่มนี้

ผมจึงได้หยิบออกมาทดสอบด้วยกัน 4 วิธีคือ

  1. Custom Mapping
  2. EmitMapper
  3. ValueInjection
  4. AutoMapper

ทุกตัวใช้ version ล่าสุดจาก nuget (prerelease option)

เพื่อหา performance ของ simple object collection จำนวน 1M ล้านตัว เพื่อเปรียบเทียบดู ก็ได้ผลตามนี้

หลักการคือต้องการ .ToList() เพื่อทำการ copy object ไปยังตัวแปรที่รับค่าจริงๆ

เห็นได้ชัดว่าแย่ที่สุดคือ ValueInjection

แย่รองลงมาคือ EmitMapper

ผลเทสดีอันดับที่สองคือ Custom Mapping

และประสิทธิภาพดีที่สุดและทำงานได้ไวสุดยกให้ AutoMapper

มาดู code ที่ใช้ทดสอบ

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using AutoMapper;

using Omu.ValueInjecter;

namespace DemoPartialUpdate

{


public class User

{


public int Id { get; set; }

public string UserName { get; set; }

public string Password { get; set; }

public DateTime LastLogin { get; set; }

}


class Program

{


static void Main(string[] args)

{


int objectLenght = 1000000;


List<User> users = new List<User>(objectLenght);


for (int i = 0; i < objectLenght; i++)

{


User user = new User();

user.Id = i;

user.UserName = “User” + i;

user.Password = “1” + i + “2” + i;

user.LastLogin = DateTime.Now;

users.Add(user);

}


Stopwatch st = new Stopwatch();


//Custom Mapping

st.Start();

var userList = users.Select(o => new User{ Id = o.Id, UserName = o.UserName, Password = o.Password, LastLogin = o.LastLogin}).ToList();

st.Stop();

Console.ForegroundColor = ConsoleColor.Green;

Console.WriteLine(“Custom mapping {0} objects within {1}, objectLenght, st.Elapsed.ToString(“g”));


//EmitMapper

st.Start();

var map = EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<User, User>();

IEnumerable<User> emitUsers = users.Select(o => map.Map(o)).ToList();

st.Stop();

Console.ForegroundColor = ConsoleColor.Blue;

Console.WriteLine(“EmitMapper mapping {0} objects within {1}, objectLenght, st.Elapsed.ToString(“g”));


//ValueInject

st = new Stopwatch();

st.Start();

IEnumerable<User> valueUsers = users.Select(o => (User)new User().InjectFrom(o)).ToList();

st.Stop();

Console.ForegroundColor = ConsoleColor.Red;

Console.WriteLine(“ValueInjecter mapping {0} objects within {1}, objectLenght, st.Elapsed.ToString(“g”));


//AutoMapper

st = new Stopwatch();

st.Start();

var userMap = Mapper.Map<IEnumerable<User>>(users).ToList();

st.Stop();

Console.ForegroundColor = ConsoleColor.Yellow;

Console.WriteLine(“AutoMapper mapping {0} objects within {1}, objectLenght, st.Elapsed.ToString(“g”));


Console.ReadKey();


}

}

}

จบครับ

24/01/2007

BLOG : จะหา TextBox หรือ อะไรซักอย่าง ใน Form หรือ Control นี้ ๆ ออกมาทั้งหมด ทำไงดี ??

Filed under: ลากมาตอบ — Nine MVP @ 11:59 pm

เนื่องจาก สืบเนื่องจากหลาย ๆ กระทู้ ถามกันมาจน ๆ  หลาย ๆ กระทู้รวมกัน นับได้หลายสิบเลยทีเดียว

 
จะหา textbox บน form ทำไง?
จะหา control นู๊นน นี้ ทำไง?
 
พี่น้องท่านทั้งหลายก็ บอกกันไป for มั้ง for each มั้ง ล่าสุดผมตอบไป
C# >>  this.Controls.Find("textbox1", true) ;
VB >> Me.Controls.Find("textbox1", true)
 
แต่มันออกมา อันเดียว แถมต้องกำหนดชื่ออีก อืม แบบนี้คงเฉพาะเจาะจงที่อยากจะหาจริง ๆ
บังเอิญอยากได้แต่ TextBox หรือcontrol อะไรซักอย่างนึงทำยังไงดี
อยากได้ method ที่ return ค่ากลับออกเป็น Control ตัวนั้น ๆ และเป็น collection ทำไงดี
จะได้ไม่ต้อง cast ให้เสียเวลา อ้าง property ทำงานต่อได้เลย
 
ก็เลยทำ Class Util ตัวนี้มาให้ใช้กันดู แต่ใช้ได้เฉพาะ .NET 2.0
เป็นการใช้ Generic Programming มา implement ใน method
และท่องไปใน child control ด้วย Recursive method ซึ่งก็เป็นส่วนประกอบของ class นี้
 
 
Code : UIHelper Class
 

/// <summary>

/// version 1.0

/// </summary>

public static class UIHelper

{

    /// <summary>

    /// This method for find a control in control’s collection with Strong Type(Generic).

    /// </summary>

    /// <typeparam name="T">Find Type</typeparam>

    /// <param name="ctrl">ControlCollection</param>

    /// <returns>List of the Generic Type’s Method</returns>

    public static List<T> FindControls<T>(Control.ControlCollection ctrl)

    {

        List<T> list = new List<T>();

        foreach (Control c in ctrl)

        {

            if (c.GetType() == typeof(T))

            {

                T cc = (T)

Convert.ChangeType(c, typeof(T));

                list.Add(cc);

            }

            else

                FindCtrlRecrs(c, ref list);

        }

        return list;

    }
 

    /// <summary>

    /// The Recursive Method for find all a child of controls with Strong Type(Generic).

    /// </summary>

    /// <typeparam name="T">Find Type</typeparam>

    /// <param name="nextControl">root of next control</param>

    /// <param name="list">bag(generic) of found a control</param>

    private static void FindCtrlRecrs<T>(Control nextControl, ref List<T> list)

    {

        if (nextControl.Controls.Count != 0)

        {

            foreach (Control c in nextControl.Controls)

            {

                if (c.GetType() == typeof(T))

                {

                    T cc = (T)

Convert.ChangeType(c, typeof(T));

                    list.Add(cc);

                }

                else if (c.HasChildren)

                    FindCtrlRecrs(c, ref list);

            }

        }

    }

}

 

 
How to use :
 
อยากได้ TextBox จงออกมาก็เรียก

List<TextBox> txts = UIHelper.FindControls<TextBox>(this.Controls);

 

อยากได้ CheckBox ออกมาก็เรียก 

List<CheckBox> txts = UIHelper.FindControls<CheckBox>(this.Controls);

 

 

28/12/2006

Blog: มาทำ Auto Hide Window แบบ TaskBar กันเถอะ (C#)

Filed under: ลากมาตอบ — Nine MVP @ 1:49 pm

 

จะปีใหม่แล้ว งานต่าง ๆ ก็น้อยลง คนทำงานก็เบามือ ๆ ลง เลยว่างนิดหน่อย แวะมาทำโปรแกรมเล่น ๆ ให้เสร็จฝากเพื่อน ๆ
 
เป็น window application ครับ เป็นตัวอย่างที่เขียนโดยใช้ C#
สมัย .NET 1.1 จะทำหน้าจอแบบนี้คงต้องใช้ Win32 API กัน
แต่หลัง .NET 2.0 ออกมาก็ลด code ได้ได้เยอะครับ สั่งแค่นิดหน่อยก็ได้งานแล้ว
และ code อีกส่วนนึงจะเป็นการทำงานแบบ multi thread โดยใช้ threadpool class เข้ามาช่วยในการทำ autohide ของ window ครับ
code ส่วนสำคัญ ๆ ผมจะ Comment ไว้ให้หมดแล้วนะครับ
 
ก็ลองเอาไปประยุกต์ต่อกันเองนะครับ คิดว่าน่าจะมีประโยชน์บ้าง 
 
อืมลองดูตามรูปข้างล่างนะครับ แถบสีฟ้า ๆ  จะเป็น window form นั้นเอง จะหุบ ๆ โผล่ ๆ หุหุ
 
 
 
 
 
 
 

Download Source Code Here!!

http://nine.greatfriends.us/AutoHideForm.rar

 
 
ใครไม่เข้าใจ code บรรทัดไหนก็ ถามมาได้นะครับ
หรือใครอยากได้ feature อะไรเพิ่ม ถ้ามีเวลาจะเพิ่มให้ครับ
 
ปล. ตอนนี้ทำ align window ได้ 2 แบบ คือ Top และ Left ครับ

18/09/2006

[Asp.net] HtmlEncode กับ UrlEncode

Filed under: ลากมาตอบ — Nine MVP @ 12:03 am
ปุจฉา:
อยากให้ช่วยอธิบายคร่าวๆ เกี่ยวกับ Server.HtmlEncode กับ Server.UrlEncode ต่างกันยังงัย เอามาใช้ตอนไหน ตัวอย่าง จะดีมากครับ


วิสัชชนา
:

Server.HtmlEncode

เอาไว้แปลงค่า String ที่ต้องการให้เป็น HTML Code ครับ
แต่ใน ASP.NET ผมมอง code ที่เป็น HTML encode ตัวหนังสือไม่ออกเลย นอกจากเป็น พวกสัญลักษณ์จะเห็นได้
TextBox1.Text = Server.HtmlEncode("http://www.myweb.com?search=TT&T&quot;)

จะได้ http://www.myweb.com?search=TT&amp;T < เห็นได้ว่า & ถูก encode เป็น &amp;

และใช้ Server.HtmlDecode เพื่อแปลงชุดสตริงกลับมา
การใช้งาน
ตัวนี้ยังไม่แน่ใจเท่าไหร่ ว่าเพื่ออะไรนะ รอผู้ใช้งานเชี่ยว ๆ มาตอบ


ส่วน Server.UrlEncode

เอาไว้ Encode String ที่ต้องการให้เป็น URL ที่ระบบเข้าใจได้ เช่น

TextBox2.Text = "http://www.myweb.com?search=&quot; & Server.UrlEncode("TT&T")

จะได้ http://www.myweb.com?search=TT%26T < เห็นได้ว่า & ถูก Encode เป็น %26

และใช้ Server.UrlEncode เพื่อแปลงชุดสตริงกลับมา

การใช้งาน

หากมีสัญลักษณ์จำเพาะใน QueryString อาจจะมีปัญหาในการรับข้อมูลพลาดได้
ดังตัวอย่างที่ยกมาให้ดู
ที่เกี่ยวกับ & หากส่งไปเป็น http://www.myweb.com?search=TT&T จะพบว่าเรารับค่าได้เพียง TT แต่ &T จะหายไป
เนื่องจาก & เป็นตัว spilter ของ QueryString แต่ละตัว
เช่น http://www.myweb.com?ID=5&Name=suichai เราจึงแก้ด้วยการ Encode

17/09/2006

เปลี่ยน DateFormat ของ Regional Setting แบบ programmatically

Filed under: ลากมาตอบ — Nine MVP @ 11:38 pm
ตอบ คุณ aora ไม่ทราบว่าใช่่ VBA ของ Access รึปล่าว หรือว่าเป็น VB6 หรือ VB.NET กรุณาระบุด้วยนะ
แต่ผมเอา VB.NET มาตอบก่อนละกันขอประทานโทษ IE7 ผมเดี้ยงตอนนี้ฝืนใช้ Firefox อยู่  format เลยดูแย่หน่อย
ก็ให้็ copy code ไปวางเลยนะ


‘อันนี้เป็น code ส่วน Win32 API    Declare Function GetLocaleInfo Lib “kernel32” Alias “GetLocaleInfoA” (ByVal Locale As IntPtr, ByVal LCType As IntPtr, ByVal lpLCData As String, ByVal cchData As IntPtr) As IntPtr

Declare Function SetLocaleInfo Lib “kernel32” Alias “SetLocaleInfoA” (ByVal Locale As IntPtr, ByVal LCType As IntPtr, ByVal lpLCData As String) As Boolean

Declare Function GetUserDefaultLCID% Lib “kernel32” ()

‘ค่าคงที่ต่าง ๆ ที่ใช้อ้างอิงใน API Function
Public Const LOCALE_ICENTURY = &H24
Public Const LOCALE_ICOUNTRY = &H5
Public Const LOCALE_ICURRDIGITS = &H19
Public Const LOCALE_ICURRENCY = &H1B
Public Const LOCALE_IDATE = &H21
Public Const LOCALE_IDAYLZERO = &H26
Public Const LOCALE_IDEFAULTCODEPAGE = &HB
Public Const LOCALE_IDEFAULTCOUNTRY = &HA
Public Const LOCALE_IDEFAULTLANGUAGE = &H9
Public Const LOCALE_IDIGITS = &H11
Public Const LOCALE_IINTLCURRDIGITS = &H1A
Public Const LOCALE_ILANGUAGE = &H1
Public Const LOCALE_ILDATE = &H22
Public Const LOCALE_ILZERO = &H12
Public Const LOCALE_IMEASURE = &HD
Public Const LOCALE_IMONLZERO = &H27
Public Const LOCALE_INEGCURR = &H1C
Public Const LOCALE_INEGSEPBYSPACE = &H57
Public Const LOCALE_INEGSIGNPOSN = &H53
Public Const LOCALE_INEGSYMPRECEDES = &H56
Public Const LOCALE_IPOSSEPBYSPACE = &H55
Public Const LOCALE_IPOSSIGNPOSN = &H52
Public Const LOCALE_IPOSSYMPRECEDES = &H54
Public Const LOCALE_ITIME = &H23
Public Const LOCALE_ITLZERO = &H25
Public Const LOCALE_NOUSEROVERRIDE = &H80000000
Public Const LOCALE_S1159 = &H28
Public Const LOCALE_S2359 = &H29
Public Const LOCALE_SABBREVCTRYNAME = &H7
Public Const LOCALE_SABBREVDAYNAME1 = &H31
Public Const LOCALE_SABBREVDAYNAME2 = &H32
Public Const LOCALE_SABBREVDAYNAME3 = &H33
Public Const LOCALE_SABBREVDAYNAME4 = &H34
Public Const LOCALE_SABBREVDAYNAME5 = &H35
Public Const LOCALE_SABBREVDAYNAME6 = &H36
Public Const LOCALE_SABBREVDAYNAME7 = &H37
Public Const LOCALE_SABBREVLANGNAME = &H3
Public Const LOCALE_SABBREVMONTHNAME1 = &H44
Public Const LOCALE_SCOUNTRY = &H6
Public Const LOCALE_SCURRENCY = &H14
Public Const LOCALE_SDATE = &H1D
Public Const LOCALE_SDAYNAME1 = &H2A
Public Const LOCALE_SDAYNAME2 = &H2B
Public Const LOCALE_SDAYNAME3 = &H2C
Public Const LOCALE_SDAYNAME4 = &H2D
Public Const LOCALE_SDAYNAME5 = &H2E
Public Const LOCALE_SDAYNAME6 = &H2F
Public Const LOCALE_SDAYNAME7 = &H30
Public Const LOCALE_SDECIMAL = &HE
Public Const LOCALE_SENGCOUNTRY = &H1002
Public Const LOCALE_SENGLANGUAGE = &H1001
Public Const LOCALE_SGROUPING = &H10
Public Const LOCALE_SINTLSYMBOL = &H15
Public Const LOCALE_SLANGUAGE = &H2
Public Const LOCALE_SLIST = &HC
Public Const LOCALE_SLONGDATE = &H20
Public Const LOCALE_SMONDECIMALSEP = &H16
Public Const LOCALE_SMONGROUPING = &H18
Public Const LOCALE_SMONTHNAME1 = &H38
Public Const LOCALE_SMONTHNAME10 = &H41
Public Const LOCALE_SMONTHNAME11 = &H42
Public Const LOCALE_SMONTHNAME12 = &H43
Public Const LOCALE_SMONTHNAME2 = &H39
Public Const LOCALE_SMONTHNAME3 = &H3A
Public Const LOCALE_SMONTHNAME4 = &H3B
Public Const LOCALE_SMONTHNAME5 = &H3C
Public Const LOCALE_SMONTHNAME6 = &H3D
Public Const LOCALE_SMONTHNAME7 = &H3E
Public Const LOCALE_SMONTHNAME8 = &H3F
Public Const LOCALE_SMONTHNAME9 = &H40
Public Const LOCALE_SMONTHOUSANDSEP = &H17
Public Const LOCALE_SNATIVECTRYNAME = &H8
Public Const LOCALE_SNATIVEDIGITS = &H13
Public Const LOCALE_SNATIVELANGNAME = &H4
Public Const LOCALE_SNEGATIVESIGN = &H51
Public Const LOCALE_SPOSITIVESIGN = &H50
Public Const LOCALE_SSHORTDATE = &H1F
Public Const LOCALE_STHOUSAND = &HF
Public Const LOCALE_STIME = &H1E
Public Const LOCALE_STIMEFORMAT = &H1003

‘ รับค่า Locale จาก regional setting
Public Sub Get_locale(ByVal InString As String)
Dim Symbol As String
Dim iRet1 As IntPtr
Dim iRet2 As IntPtr
Dim lpLCDataVar As String
Dim Pos As Integer
Dim Locale As IntPtr

Locale = GetUserDefaultLCID()

iRet1 = GetLocaleInfo(Locale, InString, lpLCDataVar, 0)
Symbol = iRet1.ToString ‘String$(iRet1, 0)

iRet2 = GetLocaleInfo(Locale, InString, Symbol, iRet1)
Pos = InStr(Symbol, Chr(0))
If Pos > 0 Then
Symbol = Symbol.Substring(Pos – 1)
MsgBox(“Regional Setting = ” + Symbol)
End If

End Sub

‘เปลี่ยน DateTime Format ใน Regional Setting
Public Sub Set_locale(ByVal InString As IntPtr, ByVal InSymbol As String)

Dim iRet As IntPtr
Dim Locale As IntPtr

Locale = GetUserDefaultLCID() ‘Get user Locale ID
iRet = SetLocaleInfo(Locale, InString, InSymbol)

End Sub


‘ อันนี้ตอนจะเรียกใช้ Code ข้างบน ไปวาง ใน Sub อะไรก็ได้

MsgBox(“ค่าเดิมของ date format = ” & Now.ToShortDateString)

Dim InString As String
Dim InSymbol As String
‘กำหนดค่าคงที่ ของ shortdate
InString = LOCALE_SSHORTDATE
‘กำหนด format จาก  MM/dd/yyyy ไปเป็น dd/MM/yyyy
InSymbol = “dd/MM/yyyy”
‘ทำการเปลี่ยน format
Set_locale(InString, InSymbol)

MsgBox(“โปรแกรมจะมองเห็น new date format ก็ต่อเมื่อมีการปิดโปรแกรมแล้วเปิดใหม่”)

Create a free website or blog at WordPress.com.