Friday, January 27, 2012

System.Collections.IEnumerable and collection initializer syntax

I'd like to make a post about something that I think is one of the least utilized aspects of C#: custom collection initializers. Implementing a custom collection initializer for your class can make your code much more declarative and concise.

It's common to have a collection of things that you set up at design time: drop down list items, configurations, items in a game, a list of mappings, all kinds of things. These are often stored in external xml files or in a sql table. If you're like me then you'd rather have this in code and have it as concise and easy to get right as possible. Here's how.

Look at this example of setting up a list of Armor types for a game:

public List<Armor> ArmorsWithListExample()
{
List<Armor> armors = new List<Armor>();
armors.Add(new Armor("leather armor", 1, 250, 10));
armors.Add(new Armor("chain mail", 4, 500, 14));
armors.Add(new Armor("splint mail", 7, 750, 16));
armors.Add(new Armor("plate armor", 10, 1000, 18));
return armors;
}

Here is the same thing using the collection initializer syntax:

public List<Armor> ArmorsWithInitializedListExample()
{
return new List<Armor>() {
new Armor("leather armor", 1, 250, 10),
new Armor("chain mail", 4, 500, 14),
new Armor("splint mail", 7, 750, 16),
new Armor("plate armor", 10, 1000, 18),
};
}

There are several advantages with the collection initializer syntax. You don't have to have the temporary variable, it's less code with fewer redundancies, and it's a nice declarative way to show that all you are doing is creating the collection. Small advantages, I admit, but they add up. But if you look at some C code, you see that the code can be even more concise by declaring the objects themselves inline. Here's an example from the game Brogue:

const itemTable armorTable[NUMBER_ARMOR_KINDS] = {
{"leather armor", "", "", 10, 250, 10, {30,30,0}, true, false, "This lightweight armor offers basic protection."},
{"scale mail", "", "", 10, 350, 12, {40,40,0}, true, false, "Bronze scales cover the surface of treated leather, offering greater protection than plain leather with minimal additional weight."},
{"chain mail", "", "", 10, 500, 13, {50,50,0}, true, false, "Interlocking metal links make for a tough but flexible suit of armor."},
{"banded mail", "", "", 10, 800, 15, {70,70,0}, true, false, "Overlapping strips of metal horizontally encircle a chain mail base, offering an additional layer of protection at the cost of greater weight."},
{"splint mail", "", "", 10, 1000, 17, {90,90,0}, true, false, "Thick plates of metal are embedded into a chain mail base, providing the wearer with substantial protection."},
{"plate armor", "", "", 10, 1300, 19, {120,120,0}, true, false, "Emormous plates of metal are joined together into a suit that provides unmatched protection to any adventurer strong enough to bear its staggering weight."}
};


It's a small improvement but I want to do that in C# as well. With custom collection initializers, you can.

public List<Armor> ArmorsWithCustomInitializerExample()
{
return new ArmorCollection() {
{ "leather armor", 1, 250, 10 },
{ "chain mail", 4, 500, 14 },
{ "splint mail", 7, 750, 16 },
{ "plate armor", 10, 1000, 18 },
}.List;
}

Look at that! Concise and easy to get right. Here's the code for the ArmorCollection; notice that it's just a wrapper around a List. The only additional functionality it has is the Add method.

class ArmorCollection : IEnumerable
{
public List<Armor> List { get; set; }

public ArmorCollection()
{
List = new List<Armor>();
}

IEnumerator IEnumerable.GetEnumerator(){
return List.GetEnumerator();
}

public void Add(string name, int weight, int cost, int ac)
{
List.Add(new Armor(name, weight, cost, ac));
}
}

The Add method is what makes it work. According to section 7.6.10.3 of the C# specs, if a class implements the IEnumerable interface then the compiler converts the initializer syntax into a series of calls to the Add method, even though the Add method isn't part of the IEnumerable interface. The Add method can have any number of parameters and the compiler will make it work.

So when you have a collection of things that isn't going to change, instead of using a built-in collection with a series of Add calls, loading from an external xml file, or loading a sql table, consider creating an in memory collection by using a custom initializer: it may be easier, faster, and more concise than the alternatives.

Tuesday, January 24, 2012

Nâng cấp ANCMedia 3.3 - add server.

Đầu năm đầu tháng khai xuân một bài. Chúc mọi người một năm mới đầy sức khoẻ, may mắn, hạnh phúc. Mình về tết cũng rảnh đôi chút, sửa lại cho mọi người tiện add server.. Chứ suốt ngày kêu ca và yêu cầu mình có 3 đầu 6 tay cũng không làm hết được. ^^

Maphim.Net | Xem phim Online, xem phim han, phim 47, tron bo
Mình đã tách riêng làm 2 file js..Mọi người có thể up lên google code. Nhưng để tiện edit các bạn nên down dropbox về và up file server lên đó.

* File server : http://anhnc.googlecode.com/svn/trunk/ANC/3.3/server.js

_Trong file server này gồm 2 phần chính: config và server.

Maphim.Net | Xem phim Online, xem phim han, phim 47, tron bo
 Danh sách id các bạn có thể thay đổi tuỳ ý. Đã có nhiều bạn có host riêng, vì vậy mình đã làm thêm config cho Player, Proxy, Skin...

* File player: http://anhnc.googlecode.com/svn/trunk/ANC/3.3/player.js

_Vẫn là player có sẵn trong site, nhưng nếu dùng file đó để + file server sẽ không còn dùng được.

* Trong site chỉ cần paste 2 file này như sau vào dưới "anc_tp" hoặc </body>. Tốt nhất là dán dưới </body>
<script src="http://anhnc.googlecode.com/svn/trunk/ANC/3.3/server.js"></script>   
<script src="http://anhnc.googlecode.com/svn/trunk/ANC/3.3/player.js"></script>   

== > Phiên bản 3.3 này mình đã cải thiện vấn đề code, các player trước nhìn rất đau mắt, khó cấu hình CSS, nhiều đoạn code HTML bị thừa. Mọi người có thể nhìn vào hình dưới.

 +) ANC_old

Maphim.Net | Xem phim Online, xem phim han, phim 47, tron bo

+) ANC_new


* ) Giờ vẫn đang nghỉ tết nên mình chưa có mạng add server lên site mình, do đó không có demo cho mọi người. Nhưng có thể xem video hướng dẫn tại đây..

*) Các lưu ý mình đã ghi trong file server, các bạn có thể đọc.

Chúc các bạn thành công !

My summary of CQRS and DDD

Recent epiphany: sagas are just ways to turn domain events into commands.

Here's my as-of-now summary of the concepts I see in many EventSourcing, CQRS, and DDD videos and blog posts.

Command: A simple message that represents a user's request to make something happen. Just data with no behavior and often named in the imperative tense.
class CloseAccount implements Command
{
public AccountId accountId;
public String reason;

public CloseAccount(AccountId accountId, String reason){
this.accountId = accountId;
this.reason = reason;
}
}

CommandHandler: A domain service that turns a command into events by calling methods on an aggregate root, publishes the resulting events, and handles transactions and persistence. Stateless with just behavior; just a task based method and sort of procedural.
class AccountCommandHandler extends CommandHandler
{
private AccountRepository _repository;
private MessageBus _messageBus;

public AccountCommandHandler(AccountRepository repository, MessageBus messageBus){
this._repository = repository;
this._messageBus = messageBus;
}

public void handle(CloseAccount command){
Account account = _repository.get(command.accountId);

account.close(command.reason);

persistAndPublishEvents(account);
}
}

Aggregate Root: A persistent domain object that turns method calls into domain events. May contain references to other domain objects as part of a parent/child relationship. Stateful and behaviorful and guaranteed to be internally consistent.
class Account extends Aggregate
{
private AccountId id;

public Account(AccountId id){
this.id = id;
super.storeEvent(new AccountCreated(this.id));
}

public void Close(CloseReason reason){
super.storeEvent(new AccountClosed(this.id, reason));
}
}

Domain Event: A simple message that represents a change to the state of an aggregate. Just data with no behavior and often named in the past tense.
class AccountClosed implements DomainEvent
{
public AccountId accountId;
public CloseReason reason;

public AccountClosed(AccountId id, CloseReason reason){
this.accountId = id;
this.reason = reason;
}
}

MessageBus: A mechanism for publishing commands and events and subscribing handlers to commands and events. May be a separate EventBus and CommandBus. The only behavior is publishing events and subscribing event and command handlers to events; the only state is what's required to track which services have subscribed to which events.
public class SimplestEventBus implements EventBus
{
private List<Handler> handlers = new ArrayList<Handler>();

public void subscribe(Handler handler)
{
handlers.add(handler);
}

public void publish(DomainEvent event)
{
for (Handler handler : handlers)
handler.handle(event);
}
}
Saga: A persistent object that turns domain events into commands. Stateful and behaviorful. The only state is what's required for knowing when to create the commands.
// Send a notice to the account's owner once the account is
// both paid off and closed. (ignore thread safety since this is a simple example)
class OwnerNotificationSaga implements Saga
{
private AccountRepository _repository;
private MessageBus _messageBus;

private List<Account> closedAccounts = new ArrayList<Account>();
private List<Account> paidOffAccounts = new ArrayList<Account>();

public OwnerNotificationSaga(AccountRepository repository, MessageBus messageBus){
this._repository = repository;
this._messageBus = messageBus;
}

public void handle(AccountClosed event){
Account account = _repository.get(event.accountId);

if (closedAccounts.contains(account))
return;

if (paidOffAccounts.contains(account)){
sendNotice(account);
paidOffAccounts.remove(account);
} else {
closedAccounts.add(account);
}
}

public void handle(AccountPaidOff event){
Account account = _repository.get(event.accountId);

if (paidOffAccounts.contains(account))
return;

if (closedAccounts.contains(account)){
sendNotice(account);
closedAccounts.remove(account);
} else {
paidOffAccounts.add(account);
}
}

private void sendNotice(Account account){
_messageBus.publish(new NotifyOwner(account.ownerId));
}
}

So, for many of the examples I've seen, a user creates Commands that are handed to a MessageBus that dispatches it to a subscribed CommandHandler. The CommandHandler loads the relevant Aggregate and calls methods on it. The resulting events are gathered, persisted (if doing Event Sourcing), and passed to the MessageBus. The subscribers to that event will often update some read model (effectively turning domain events into updates on report tables) but they may be Sagas that track events and create related commands when necessary.

None of that is the essence of DDD or the essence of CQRS, but it's so common to the discussions and examples that this little glossary helps me to keep things straight.

Tuesday, January 17, 2012

Typefull Dynamic API: a typesafe C# facade/adapter/monad with phantom types

One of the things I came up with at home and actually got to implement at work is so useful, and novel to the C# crowd, that I thought I'd share. I call it a Typefull Dynamic API and it looks like an object that changes its public api at design time based on what would be the state of another object at runtime. Impossible? Nope. I'll even show you how you could have came up with it yourself.

The scenario

Imagine the following fictitious method:
public Account Example()
{
var account = new Account();
account.State = UsaState.California;
account.Open();
PeopleManager.SetAccountPeople(account, 0);
MemberLevelServiceHelper.SetAccountHolderLevel(account, MemberLevel.Gold);
new DebtCalculator().CalculateDebt(account);
account.Complete();
Reports.GetCompletedAccountReport().AddAccount(account);
AccountUtil.Close(account);
NoteAddingSingleton.Instance.AddNote(account, "I'm an example!");
return account;
}
It looks like it creates an account, does some setup, and returns it. It's calling a bunch of things from all over the place. I know from experience that I'd have a hard time remembering where everything is and what order to call it in. Some things are on the account, some are singletons, some are helpers and utils - exactly the kind of situation that anyone who's joined a long term project with many other developers is familiar with. If only there was a unified interface for all these subsystems - oh wait - isn't that the definition of the classic Facade pattern?

The patterns

class AccountLifecycleFacade
{
public Account RealAccount { get; set; }

public AccountLifecycleFacade()
{
RealAccount = new Account();
}

public void SetState(UsaState state)
{
RealAccount.State = state;
}

public void Open()
{
RealAccount.Open();
}

public void SetAccountPeople(int count)
{
PeopleManager.SetAccountPeople(RealAccount, count);
}

public void MakeGoldLevel()
{
MemberLevelServiceHelper.SetAccountHolderLevel(RealAccount, MemberLevel.Gold);
}

/* ... etc ... */
}
Having this facade lets us work through the lifecycle of an account without worrying about where that functionality is actually implemented; no more trying to remember which helper, manager, singleton, service, utility, or business class to use since we only need to look at one place. With one class for the hi-level functionality, we have a clear api and intellisense will show us what we can do with an account.
public Account FacadeExample()
{
var account = new AccountLifecycleFacade();
account.SetState(UsaState.California);
account.Open();
account.SetAccountPeople(0);
account.MakeGoldLevel();
account.CalculateDebt();
account.Complete();
account.AddToCompletedAccountReport();
account.Close();
account.AddNote("I'm an example!");
return account.RealAccount;
}
And since it works with a single account at a time we can make it a wrapper. If we make the wrapper return itself at the end of each method then we'll be able to call it using a fluent interface.
class AccountLifecycleAdapter
{
public Account RealAccount { get; set; }

public AccountLifecycleAdapter(Account account)
{
RealAccount = account;
}

public AccountLifecycleAdapter SetState(UsaState state)
{
RealAccount.State = state;
return this;
}

public AccountLifecycleAdapter Open()
{
RealAccount.Open();
return this;
}
/* ... etc ... */
}
And now our example method is a little simpler.
public Account Example()
{
var account = new AccountLifecycleAdapter(new Account())
.SetState(UsaState.California)
.Open()
.SetAccountPeople(0)
.MakeGoldLevel()
.CalculateDebt()
.Complete()
.AddToCompletedAccountReport()
.Close()
.AddNote("I'm an example!");

return account.RealAccount;
}
That's much easier for me to read, understand, and test. I'm sure this facade/adapter has been done a hundred times before. I see one potential problem though: it looks like some of the methods are only valid at certain times. I'd wager that you can't complete a non-open account and that you really shouldn't add an account to the Closed Account Report until it's closed. We could rely on runtime assertions to verify the preconditions and post conditions of our new Account api, which is a fine idea, but wouldn't it be nice if we could catch this at compile time?

The magic

If the preconditions and postconditions of these methods were expressed in the type system then not only would the compiler stop us from running invalid code, but intellisence would only show the methods that are valid for the account we have. In order for that to work we need to change the type of our AccountLifecycleAdapter to expose the state of the account. For this example, certain things may only be valid when the account is new, opened, completed, or closed and certain things may only be valid for new, bronze, silver, or gold members. Not only do these methods have preconditions based on the status and member level of the account, but they also have postconditions that change the state of the account.

Some preconditions and post conditions of the domain methods.
MethodPreconditionPostcondition
Open()Status = NewStatus = Open
Complete()Status = OpenStatus = Complete
Close()Status = Open or CompleteStatus = Close
AddToCompletedAccountReport()Status = CompleteStatus is unchanged

Although we can change the state of an object, we can't change its type. Luckily we don't need to since we are returning an AccountLifecycleAdapter with each method: we can return a different type that represents the state of the account and only supports the methods that are valid for that account state.

One possible way is to have a new class for each possible state.
class AccountLifecycleAdapter_NewStatus_NoLevel
{
public Account RealAccount { get; set; }
public AccountLifecycleAdapter_NewStatus_NoLevel(Account account)
{
RealAccount = account;
}

public AccountLifecycleAdapter_NewStatus_NoLevel SetState(UsaState state)
{
RealAccount.State = state;
return this;
}

public AccountLifecycleAdapter_OpenStatus_NoLevel Open()
{
RealAccount.Open();
return new AccountLifecycleAdapter_OpenStatus_NoLevel(RealAccount);
}

/* ... etc ... */
}
This would make sure that the wrapper expresses the state of the account and that only valid methods are called for that account. But it could become a cartesian explosion; this example as 4 statuses and 4 member levels for a total of 16 classes you need to make. Since some things are applicable for several states (like CalculateDebt and Close from our earlier table), they'd need to be implemented in several different wrapper classes. Messy. Fortunately for C# users, the language offers a convenient way out by way of generics and extension methods. Instead of having the class name express the state, we can use a generic type parameter for each run time variable we wish to track; status and member level in this case. A method that creates a copy with the type parameters we want would let us chain the wrapped object along while changing the type parameters.

class AccountLifecycleAdapter<Status, Level>
{
public Account RealAccount { get; set; }
public AccountLifecycleAdapter(Account account)
{
RealAccount = account;
}

public AccountLifecycleAdapter<NewStatus, NewLevel> SetTypes<NewStatus, NewLevel>()
{
return new AccountLifecycleAdapter<NewStatus, NewLevel>(RealAccount);
}
}


And a non-generic version will make sure we create the adapter with the correct default type parameters.

public class AccountLifecycleAdapter
{
public static AccountLifecycleAdapter<New, NoLevel> New(Account account)
{
return new AccountLifecycleAdapter<New, NoLevel>(account);
}
}


The methods that used to belong to the wrapper can now be moved into extension methods.

 public static class AccountLifecycleAdapterExtensions 
{
public static AccountLifecycleAdapter<New,NoLevel> SetState(this AccountLifecycleAdapter<New,NoLevel> adapter, UsaState state)
{
adapter.RealAccount.State = state;
return adapter.SetTypes<New,NoLevel>();
}

public static AccountLifecycleAdapter<Open,L> Open<L>(this AccountLifecycleAdapter<New,L> adapter)
where L : AccountLevel
{
adapter.RealAccount.Open();
return adapter.SetTypes<Open,L>();
}

public static AccountLifecycleAdapter<Open,L> SetAccountPeople<L>(this AccountLifecycleAdapter<Open,L> adapter, int count)
where L : AccountLevel
{
PeopleManager.SetAccountPeople(adapter.RealAccount, count);
return adapter;
}

/* ... etc ... */
If we look at our extension methods then we see something I think is very interesting. With this kind of typefull api, the input parameter type represents the precondition and the return type represents the post condition.
public static AccountLifecycleAdapter<Open,L> Open<L>(this AccountLifecycleAdapter<New,L> adapter) 
where L : AccountLevel
MethodPreconditionPostcondition
OpenStatus = NewStatus = Open

Since the valid status is expressed as a precondition and the what the method changes is expressed as the return type, the compiler and intelisense can make sure you only chain methods that make sense. If a method returns an AccountLifecycleAdapter<Open, Silver> then you can't call an extension method that expects a Closed account. Intellesence won't suggest invalid methods and the compiler won't allow them. As an added bonus, changing the preconditions or post conditions in a way that breaks code will prevent a compile instead of lying dormant until discovered, investegated, and fixed during manual or automated testing.

All we need now are the types that represent the runtime state.

The Phantom Types

These types are a little odd in that they are only used for compile time info and we never create instances of them. The Haskell community would call them Phantom Types so we should too.
namespace PhantomTypes
{
public abstract class PhantomType {}

public abstract class AccountStatus : PhantomType {}
public abstract class New : AccountStatus {}
public abstract class Open : AccountStatus {}
public abstract class Completed : AccountStatus {}
public abstract class Closed : AccountStatus {}

public abstract class AccountLevel : PhantomType {}
public abstract class NoLevel : AccountLevel {}
public abstract class Bronze : AccountLevel {}
public abstract class Silver : AccountLevel {}
public abstract class Gold : AccountLevel {}
}
I like to make it double obvious that these are different than normal types with a separate PhantomType namespace and PhantomType base class.

Since the type parameters are part of the method signature, you can have different overloaded methods for different states. Here we can see that an overdraft will close the account unless it's a gold level account; they just get a warning.
    public AccountLifecycleAdapter<S,GoldLevel> Overdraft<L>(this AccountLifecycleAdapter<S,GoldLevel> adapter)
where S : AccountStatus
{
NotificationService.GetInstance().NotifyOfOverdraft(adapter.Account);
return adapter.SetTypes<S,GoldLevel>()
}

public AccountLifecycleAdapter<Closed,L> Overdraft<S,L>(this AccountLifecycleAdapter<S,L> adapter)
where S : AccountStatus
where L : AccountLevel
{
AccountUtil.CloseDueToOverdraft(adapter);
return adapter.SetTypes<Closed,L>()
}
Interfaces can be used if an extension method will work with multiple different phantom types.
  public abstract class AccountStatus : PhantomType {}
public interface NotNew { }
public abstract class New : AccountStatus {}
public abstract class Open : AccountStatus, NotNew {}
public abstract class Complete : AccountStatus, NotNew {}
public abstract class Closed : AccountStatus, NotNew {}
And the method that uses it would add that as a type parameter constraint.
     public static AccountLifecycleAdapter<S,L> AddNote<S,L>(this AccountLifecycleAdapter<S,L> adapter, string note)
where S : AccountStatus, NotNew
where L : AccountLevel
{
NoteAddingSingleton.Instance.AddNote(adapter.RealAccount, note);
return adapter;
}

Where to go from here

A lot more functionality and safety can be added. One idea is to add a check to the adapter's constructor or SetTypes method that asserts the phantom types match the actual run time state. This would let you know if your expectations of what's going on are correct or not.

public void CheckTypes<NewStatus, NewLevel>()
{
if (typeof(GoldLevel).IsAssignableFrom(typeof(NewLevel)) && RealAccount.MemberLevel != MemberLevel.Gold)
throw new InvalidOperationException("expected account member level Gold; got " + RealAccount.MemberLevel);
}

Another thing the adapter could do is collect all the ancillary objects that get created along the way. That way, once you're done with it you not only have your account, or whatever you were making, but any other related objects and you won't need to query for them.

One idea I haven't tried (yet...) is generating documentation about the system based on the adapter's extension methods. If you think about it, each of these methods is a transition from preconditions to postconditions. Each precondition and postcondition would be a state. You could use reflection, or old fashioned string manipulation, to capture all of the states and transitions within an adapter and create a state transition diagram. In a complex system this could be very useful for explaining the system to new hires, managers, users, other developers, or testers. Speaking of testing, knowing all of the different states transitions would also help ensure you are adequately testing all code paths.

I'm not entirely sure what to call this construct or "pattern". It's sort of the opposite of the State pattern; instead of an object changing behavior at run time, it's exposing a different api at design time. It's sort of like a Builder since you're building an object through a path that's more complex than a simple constructor. It can be a Facade and call other sub systems, but it doesn't have to be. It's also an Adapter since it exposes functionality related to the wrapped object. In my mind, the important thing is that it this is an adapter that only exposes methods that are valid for the run time state of the wrapped object. For now I'm calling this a Typefull Dynamic API but there may be a better name.

The Summary

Using the facade pattern can greatly aid in readability, understandability, and testability of a complex system with many subsystems - that's been known by many people for many years now. Using an object that returns itself in each method makes fluent interfaces possible - that's been known for a few years too. And using the signature of the method to express the preconditions and postconditions and making sure things are only called on object with the correct state has been around for decades, although I think the object oriented crowd has missed out on a lot of this since you can't change the type of an object when its state is changed. Extension methods give C# developers a chance to combine these in a useful way to get something new: a Typefull Dynamic API that changes its public api at design time based on what would be the state of another object at runtime.

Thursday, January 12, 2012

CÁCH XÂY DỰNG SCREENSAVER cho Windows

Về cơ bản, screensaver là một tập tin có khả năng tự thực thi (executable). Do đó, nó có thể phát triển bằng hầu hết các ngôn ngữ lập trình. Trong bài hướng dẫn này, tôi sẽ hướng dẫn cho các bạn cách xây dựng một screensaver bằng C#.
Trước khi thực thi điều này, bạn cần biết rằng chương trình quản lý screensaver của windows sử dụng các tham số dòng lệnh sau đây để triệu gọi ứng dụng screensaver:

  • Tham số /c: truyền khi triệu gọi việc thiết lập tham số cho ứng dụng screensaver. 
  • Tham số /s: truyền khi triệu gọi ứng dụng screensaver. 
  • Tham số /p: truyền khi triệu gọi chế độ preview của ứng dụng screensaver. 


Dù trong ví dụ này, minh họa cho việc xây dựng screensaver bằng C#, nhưng bạn hoàn toàn có thể phát triển bằng các ngôn ngữ khác như: C++, Delphi, Java,… theo hướng dẫn này.
Bước 1. Tạo Hai Form trong C# và đặt tên là frmScreensaver và frmSetting.
Bước 2. Xây dựng ứng dụng screensaver cho form frmScreensaver. Bạn thực thi chức năng tùy ý trên form này, nhưng hãy xây dựng một chức năng cho phép đóng form khi chuột thay đổi vị trí (hoặc đơn thuần là nhấp vào form này thì đóng lại – tức triệu gọi phương thức Close). Ngoài ra, bạn cần hiệu chỉnh cho form frmScreensaver các tham số sau:

  • Windows State: Maximized - cho phép form phóng to toàn màn hình.
  • TopMost: True - cho phép ứng dụng screensaver chạy lên trên các ứng dụng khác.
  • Start Position: CenterScreen - hiển thị ở trung tâm màn hình.
  • FormBorderStyle: None - không hiển thị viền và thanh tiêu đề của form.
  • ShowOnTaskbar: False - không hiển thị trên thanh taskbar.
  • ShowIcon: None - Không hiển thị Icon.

Bước 3. Xây dựng chức năng thiết lập cho form frmSetting. Các thông số của chương trình được lưu vào một file tạm, hoặc registry, hoặc INI file. Trong ví dụ này, tôi chỉ hướng dẫn cách ghi ra file tạm để đơn giản hóa công việc. Để lưu tham số ra file văn bản, ta sử dụng StreamWriter nằm trong System.IO. Để thuận tiện, bạn nên lưu mỗi tham số trên một dòng.
System.IO.StreamWriter wr = new System.IO.StreamWriter(đường dẫn đến tập tin);
wr.Write(Các tham số);
wr.Close();
Bước 4. Đọc các tham số trong tập tin đã lưu để tải lên form frmScreensaver. Để đọc thông tin trong file văn bản, ta sử dụng StreamReader nằm trong System.IO.
System.IO.StreamReader rd = new System.IO.StreamReader(đường dẫn đến tập tin);
string s = rd.ReadLine();
if (!s.Trim().Equals(""))
//nếu file thiết lập đã được tạo
else
//nêu file thiết lập chưa được tạo
Bước 5. Thiết lập cho trình quản lý screensaver của windows. Ta sẽ kiểm tra tham số dòng lệnh. Nếu tham số dòng lệnh nhận giá trị “/s” thì khởi động form frmScreensaver; nếu nhận giá trị “/c” thì khởi động form frmSetting.
        static void Main(string[] arg)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (arg[0].ToLower().Trim().Substring(0, 2).Equals("/s"))
Application.Run(new frmScreensaver());
else
if (arg[0].ToLower().Trim().Substring(0, 2).Equals("/c"))
Application.Run(new frmSetting());

}
Bước 6. Biên dịch chương trình. Ta thu được tập tin exe. Hãy đổi tên tập tin này lại thành tập tin .scr. Sau đó, copy tập tin này vào trong thư mục windows. Đến đây xem như đã hoàn tất. Hãy bật trình quản lý screensaver của windows để xem ứng dụng mà bạn vừa mới tạo.

Tuesday, January 10, 2012

The simplest typesafe EventBus

I'm a big fan of typesafety so while exploring messaging and events, I've also tried my own typesafe EventBus:

public static class EventBus
{
private static Dictionary<Type, List<Action<object>>> handlers 
= new Dictionary<Type, List<Action<object>>>();

public static void Subscribe<T>(Action<T> handler){
if (!handlers.ContainsKey(typeof(T)))
handlers.Add(typeof(T), new List<Action<object>>());

handlers[typeof(T)].Add(e => handler((T)e));
}

public static void Publish<T>(T e){
var eventType = typeof(T);

foreach (Type handlerType in handlers.Keys)
TryPublishForType(handlerType, eventType, e);
}

private static void TryPublishForType(Type handlerType, Type eventType, object e){
if (handlerType.IsAssignableFrom(eventType))
handlers[handlerType].ForEach(handler => handler(e));
}
}

It's very similar to the simplest event bus. I'm sure there are other ways of tracking what subscribers can handle each event but the Dictionary<Type, List<Action<object>>> works well enough. If you're not used to Haskell or C# type trickery then it may seem too complex either way but I think much of that is caused by how verbose C# tends to be.

One neat thing about typesafe event busses is that, if designed for it like this one is, they can respect the inheritance tree. That means if an Action<object> is subscribed, then it will get everything that get's published since all events are assignable to object. This also means that it is backwards compatible with the simplest EventBus. A few subscribers may need to explicitly state the event type they are interested in but if they use object as the type parameter when subscribing, then the behavior should be the same.

Wednesday, January 4, 2012

The simplest EventBus

I've been playing a lot with event buses, domain events, messaging, or whatever you want to call it. I've seen some open source event buses but they looked pretty complicated and often had a lot of extra baggage that clouded what I was interested in: exploring different ways to decouple collaborating classes. I had enough of that and set out to find the simplest thing that would get the job done and here's what I came up with:
public static class EventBus
{
private static List<Action<object>> handlers = new List<Action<object>>();

public static void Subscribe(Action<object> handler)
{
handlers.Add(handler);
}

public static void Publish(object e)
{
handlers.ForEach(handler => handler(e));
}
}

That's it. No unsubscribing, multithreading, xml configuration, flexible dispatching strategies, subscription tokens, base classes, interfaces, or attributes. You don't have to use any specific interfaces to subscribe or publish. You don't even have to instantiate it - it's always ready. Anything can subscribe, anything can publish, from anywhere in your project. Anything can be published and every subscriber will get it. Want to publish an AccountClosed domain event? Go for it. Want to publish a naked string or int? Go ahead. How about publishing your entire Windows Form? I'm not sure why you would, but this won't stop you.

Just one line to wire-up a subscription (often called from Main):
public static void AddSubscriptions(ILogger logger, PopulationReport pop, MainApp app)
{
EventBus.Subscribe(e => logger.Log(e.GetType().Name + ":" + e.ToString()));

EventBus.Subscribe(app.HandleEvent);

EventBus.Subscribe(e => {
if (e is CreatureCreated)
pop.CreatureCreated(((CreatureCreated)e).Creature);
else if (e is CreatureDied)
pop.CreatureDied(((CreatureCreated)e).Creature);
});
}

And one line to publish (often called from domain objects):
public void Die()
{
this.health = 0;
EventBus.Publish(new CreatureDied(this));
}

It can be a bit on the slutty side since it's globally accessible and subscribers will get everything that's published, but that comes with benefits too. This is a good starting point and I haven't had any problems with it: the type checking is minimal for subscribers, I've never tried to subscribe a null, and I don't mind having my domain objects explicitly depend on it. You could add a ClearSubscribers method or a SetInstance method for unit testing, but I just use a single subscriber that dumps all events into a list when a unit test needs to ensure that specific events were created. In the last few at home projects I've worked on this has been the only static class I've used since this is one of the very few times that I'm in favor of a static class with behavior and state.

I find it much easier to learn about something when it's striped of all the unnecessary fluff. Event busses, domain events, messaging, whatever you want to call it is, in my mind at least, really just easy, ubiquitous, and loosely coupled communication.

Tuesday, January 3, 2012

[Updated] K14 - Pro Menu (với hiệu ứng prodown)

Lấy ý tưởng từ việc thực hiện demo cho bài viết Tạo Menu Thanh menu ngang có sổ dọc xuống mình dự định sẽ updated thủ thuật lên bằng việc tạo các giao diện khác nhau cho menu khi xem ở các trang khác nhau, ví dụ như các trang label. Nhưng sau đó mình nhớ đến kênh14 cũng có menu như vậy (chỉ khác là không có dropdown) mà giao diện lại đẹp nữa, nên mình quyết định Rip menu này. Và ở bài này menu sẽ không có hiệu ứng dropdown, mình dự định sẽ thêm hiệu ứng dropdown trong thời gian tới.


Xem DEMO

Hình ảnh minh họa :

Updated: Đã cập nhật hiệu ứng sổ dọc cho menu, các bạn có thể xem ở bên dưới

Do làm biếng design giao diện cho nó, nên mình lấy hình nên bên kênh 14 về rồi fix lại để giới thiệu cho mọi người. Và sau đây là 1 số tính năng của nó :
- Tự động thay đổi giao diện của menu cho các trang định sẵn.
- có hiệu ứng trang hiện hành (current) trên menu.
- có 6 kiểu giao diện cho các lựa chọn để thay đổi.

Sau đây là các bước thực hiện :
- Tạo 1 widget HTML/javascript ở nơi muốn đặt menu (tốt nhất là trên header của blog), sau đó dán đoạn code bên dưới vào :
<style type="text/css">
#promenu ul li a:hover {text-decoration:none;}
#promenu {
background-repeat: repeat-x;
background-image: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivgYcRSBwnBNiJsle55vTMn4n0k-1ppAyaKCOT0II51ZkSAcqRZQOJd8vdAxdoRPTOXbdDoqSkbtp3MSgdEhguVP9prLASoyzqCHf3J0NbJSl8tJW3cA2F4qKBfzayhOB1uKe2TEZhyphenhypheneql/s400/1-6-bg.png);
width: 100%;
height: 32px;
}
.menu {
list-style: none;
width: 100%;
margin: auto!important;
}
.menu li {
float: left;
text-align: center;
padding:0!important;
}
.menu li a {
color: white;
}
.menu .active, .menu li:hover {
background-position: 0 -34px;
float: left;
}
.menu .active span, .menu span:hover {
background-position: 100% -34px;
float: left;
line-height: 30px;
height: 30px;
}
.menu li a span {
font: 12px/31px Tahoma;
font-weight: 700;
display: block;
text-align: center;
padding: 0 8px 0 9px;
}
.menusep {
background-position: 0 0;
height: 32px;
line-height: 32px;
width: 2px;
}
</style>

<script type="text/javascript">
//<![CDATA[
var style1=["0px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjB_qrIHJUU5DQRcfgQl-YIKYjohct7cYf8ewjXcHSs7c6vLXRzb4t6evkEdJua2s3d5iEc4guYOFXO7esOY3WSgytkmknf27qz9fM3g-uEx_jQEUGhs9CvXYfsEyYuq7cn2akK_HFoVPw_/s318/style1-sp.png"];
var style2=["-40px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTqOntwRQqU9VF4J4kkWSIZzSNXAEhXiueSdBP-gIuyvquFyXdh9d5H1JC1PosFcAWXS9KNI10fyCd45BnV5yjxlkAF02vrGU_ZRdZrmseQ42s6j7Ta1nhD0rxgp3x_lYLKQBtRNj6mUY9/s318/style2-sp.png"];
var style3=["-80px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtTN6BHISwwbYMZNQObYcA5GeFdVjTINiROvkhR6yY02WbllPgX5AYjuByBmdHkVNcaF9B0jmjaOZcIYPIArTdTeNJ3CHaFyyJ2UTt9yumUtFs0_eMmaQS0k9MrKqGa6ux9D8LFgUf0k9S/s318/style3-sp.png"];
var style4=["-120px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq73_C2ve2QYjG_-c21IILozgLXRb5LHXBm4UvxrY9zOCl4je5UYDR4O2fPbxCp0XkWurDEbB_mLctSCIWwZRVL68oChb0aRtQ2m08zI6zUvi3-Bhcj_SZKaEE9gtClxNoxeLEM9DM6d0H/s318/style4-sp.png"];
var style5=["-160px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJGlExjcgENp_Rl0bb6kt1riavFd_Xi746JnRp7rRmpXa2ElH_6lKuDfYNCei6v9PpQoEBh7zSj7-ypAWh1lGb1qJFoRYDj-c3Bhn3dmWVa13vYUrCpnLGtrrI4kxJbVkjHGKgvRBEAMr8/s318/style5-sp.png"];
var style6=["-200px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieunm61x5Wmj0NBUJI_hGueTyIK_b3_5ZxAlcpeBMofbkgMFlQ1OrUA6DqbLHbAvvKdx5-DOinVWEBvotu9uo8yjFsi6u4DYAHRCn9su0SRUReFzhciuUubZUZHD5PnX1DKjbyXX3DtVsy/s318/style6-sp.png"];

var m0=["http://demo.fandung.com","Home","home_id"];
var m1=["link_menu1","Menu1","m_id1"];
var m2=["link_menu2","Menu2","m_id2"];
var m3=["link_menu3","Menu3","m_id3"];
var m4=["link_menu4","Menu4","m_id4"];
var m5=["link_menu5","Menu5","m_id5"];
var m6=["link_menu6","Menu6","m_id6"];

var ttmenu = [m0,m1,m2,m3,m4,m5,m6];

var n=ttmenu.length;
var list_menu=[];
var list_active=[];
var c_menu='';
var m_homepage='http://demo.fandung.com';
var h_cond=m_homepage+'/search';

var active_cond = location.href;
var home_cond=active_cond.split("?")[0];

var bg_pos='';
var sp_img='';

if (active_cond.indexOf(ttmenu[1][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else if (active_cond.indexOf(ttmenu[2][0])!=-1) {bg_pos=style3[0];sp_img=style3[1];}
else if (active_cond.indexOf(ttmenu[3][0])!=-1) {bg_pos=style4[0];sp_img=style4[1];}
else if (active_cond.indexOf(ttmenu[4][0])!=-1) {bg_pos=style5[0];sp_img=style5[1];}
else if (active_cond.indexOf(ttmenu[5][0])!=-1) {bg_pos=style6[0];sp_img=style6[1];}
else if (active_cond.indexOf(ttmenu[6][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else {bg_pos=style1[0];sp_img=style1[1];}

var mcss = document.createElement('style');
mcss.type = 'text/css';
mcss.innerHTML = '#promenu {background-position: 0 '+bg_pos+'} .menu li:hover, .menu .active, .menu .active span, .menusep, .menu span:hover {background-image: url('+sp_img+')}';
document.body.appendChild(mcss);

c_menu +='<div id="promenu"><ul class="menu">';

for (var i=0;i<n;i++) {

if ((home_cond==m_homepage)||(home_cond==h_cond)) {list_active[0]='class="active"';}
else if (active_cond.indexOf(ttmenu[i][0])!=-1) {list_active[i]='class="active"';} else {list_active[i]='';}

list_menu[i]='<li><a href="'+ttmenu[i][0]+'" '+list_active[i]+' id="'+ttmenu[i][2]+'"><span>'+ttmenu[i][1]+'</span></a></li>';
c_menu +=list_menu[i];
if (i<n-1) {c_menu +='<li class="menusep"> </li>';}
}
c_menu +='</ul></div>';
document.write(c_menu);

//]]>
</script>

Một vài hướng dẫn :
- Việc đầu tiên là thay http://demo.fandung.com thành tên miền của blog bạn, nhớ là không có dấu (/) theo sau tên miền.
- Để thêm menu, bớt menu, thay đổi tên hiển thị của menu, thay đổi link liên kết của menu,... các bạn chỉnh sửa ở đoạn code như bên dưới :
...
...
var m0=["http://demo.fandung.com","Home","home_id"];
var m1=["link_menu1","Menu1","m_id1"];
var m2=["link_menu2","Menu2","m_id2"];
var m3=["link_menu3","Menu3","m_id3"];
var m4=["link_menu4","Menu4","m_id4"];
var m5=["link_menu5","Menu5","m_id5"];
var m6=["link_menu6","Menu6","m_id6"];

var ttmenu = [m0,m1,m2,m3,m4,m5,m6];
...
...
- http://demo.fandung.com thay bằng địa chỉ trang chủ của blog bạn
- link_menu1, link_menu2, ... là đia chỉ liên kết của các menu.
- Menu1, Menu2, ... là tên hiển thị của các menu.
- nếu muốn thêm nhiều menu nữa thì các bạn thêm code tương tự như bên dưới :
...
...
var m0=["http://demo.fandung.com","Home","home_id"];
var m1=["link_menu1","Menu1","m_id1"];
var m2=["link_menu2","Menu2","m_id2"];
var m3=["link_menu3","Menu3","m_id3"];
var m4=["link_menu4","Menu4","m_id4"];
var m5=["link_menu5","Menu5","m_id5"];
var m6=["link_menu6","Menu6","m_id6"];
var m7=["link_menu7","Menu7","m_id7"];
var m8=["link_menu8","Menu8","m_id8"];


var ttmenu = [m0,m1,m2,m3,m4,m5,m6,m7,m8];
...
...
- để tùy chọn style cho menu ở mỗi trang, các bạn chỉnh sửa ở đoạn code bên dưới :
...
...
if (active_cond.indexOf(ttmenu[1][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else if (active_cond.indexOf(ttmenu[2][0])!=-1) {bg_pos=style3[0];sp_img=style3[1];}
else if (active_cond.indexOf(ttmenu[3][0])!=-1) {bg_pos=style4[0];sp_img=style4[1];}
else if (active_cond.indexOf(ttmenu[4][0])!=-1) {bg_pos=style5[0];sp_img=style5[1];}
else if (active_cond.indexOf(ttmenu[5][0])!=-1) {bg_pos=style6[0];sp_img=style6[1];}
else if (active_cond.indexOf(ttmenu[6][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else {bg_pos=style1[0];sp_img=style1[1];}
...
...
- ttmenu[1][0], ttmenu[2][0], ..., ttmenu[6][0] ; đây là các phần tử trong mảng 2 chiều ttmenu, tương ứng với địa chỉ liên kết của các menu : Menu1, Menu2, ..., Menu6
- ở trên menu0, tức là menu HOME mình cho style mặc định là style1. cùng style với các trang liên kết không có trong menu (ví dụ các trang archive hoặc trang bài viết, ...) , hoặc các liên kết không được chọn style để hiển thị.
- code trên sẽ được hình dung như bên dưới :
Menu0 ------ style1
Menu1 ------ style2
Menu2 ------ style3
Menu3 ------ style4
Menu4 ------ style5
Menu5 ------ style6
Menu6 ------ style2
Trang khác ------ style1
- Nếu muốn thay đổi style cho các trang, các bạn chỉ cần đổi tên style1, style2,... thành các style khác là được. tức là các vị trí trong code bên dưới :
...
...
if (active_cond.indexOf(ttmenu[1][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else if (active_cond.indexOf(ttmenu[2][0])!=-1) {bg_pos=style3[0];sp_img=style3[1];}
else if (active_cond.indexOf(ttmenu[3][0])!=-1) {bg_pos=style4[0];sp_img=style4[1];}
else if (active_cond.indexOf(ttmenu[4][0])!=-1) {bg_pos=style5[0];sp_img=style5[1];}
else if (active_cond.indexOf(ttmenu[5][0])!=-1) {bg_pos=style6[0];sp_img=style6[1];}
else if (active_cond.indexOf(ttmenu[6][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else {bg_pos=style1[0];sp_img=style1[1];}
...
...

- Nếu như có nhiều menu thì các bạn cứ thêm code như bên dưới để chọn style cho nó :
...
...
if (active_cond.indexOf(ttmenu[1][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else if (active_cond.indexOf(ttmenu[2][0])!=-1) {bg_pos=style3[0];sp_img=style3[1];}
else if (active_cond.indexOf(ttmenu[3][0])!=-1) {bg_pos=style4[0];sp_img=style4[1];}
else if (active_cond.indexOf(ttmenu[4][0])!=-1) {bg_pos=style5[0];sp_img=style5[1];}
else if (active_cond.indexOf(ttmenu[5][0])!=-1) {bg_pos=style6[0];sp_img=style6[1];}
else if (active_cond.indexOf(ttmenu[6][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else if (active_cond.indexOf(ttmenu[7][0])!=-1) {bg_pos=style3[0];sp_img=style3[1];}
else if (active_cond.indexOf(ttmenu[8][0])!=-1) {bg_pos=style4[0];sp_img=style4[1];}

else {bg_pos=style1[0];sp_img=style1[1];}
...
...
- lưu ý : ttmenu[7][0], và ttmenu[8][0] là liên kết của các Menu7, và Menu8.
- nếu muốn chỉ 1 trang nào đó có giao diện đặc biệt khác với các trang còn lại (ở đây mình ví dụ là trang Menu1) thì đoạn code ở trên sẽ chỉ còn như bên dưới :
...
...
if (active_cond.indexOf(ttmenu[1][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else {bg_pos=style1[0];sp_img=style1[1];}
...
...
- Đoạn code trên có nghĩa là chỉ có trang Menu1 là có giao diện riêng biệt là Style2, còn các trang khác sẽ có giao diện Style1.

Như vậy mình hướng dẫn đã xong. các bạn cứ test thử, nếu có trục trặc gì mình sẽ fix lại bài viết và cập nhật thêm hướng dẫn.

----- UPDATED -----
K14 - Promenu (với hiệu ứng sổ dọc)
- ở bản update này, mình sẽ cập nhật thêm tính năng thêm submenu cho menu chính. Tính năng này mình kết hợp từ bài viết "Tạo menu nằm ngang với hiệu ứng sổ dọc".
- Để thực hiện việc cập nhật này, các bạn phải xem lại bài viết Tạo Menu Thanh menu ngang có sổ dọc xuống

Xem DEMO

Hình ảnh minh họa :

- Sau đây là code mẫu của thủ thuật đã updated, các bạn thực hiện theo hướng dẫn của bài này và bài Tạo Menu Thanh menu ngang có sổ dọc xuống để có thể chỉnh sửa lại từ code mẫu :
<script src="http://fandung.googlecode.com/svn/trunk/js/dropdown2.js" type="text/javascript"></script>

<style type="text/css">
#promenu ul li a:hover {text-decoration:none;}
#promenu {
background-repeat: repeat-x;
background-image: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivgYcRSBwnBNiJsle55vTMn4n0k-1ppAyaKCOT0II51ZkSAcqRZQOJd8vdAxdoRPTOXbdDoqSkbtp3MSgdEhguVP9prLASoyzqCHf3J0NbJSl8tJW3cA2F4qKBfzayhOB1uKe2TEZhyphenhypheneql/s400/1-6-bg.png);
width: 100%;
height: 32px;
}
.menu {
list-style: none;
width: 100%;
margin: auto!important;
}
.menu li {
float: left;
text-align: center;
padding:0!important;
}
.menu li a {
color: white;
height:30px;
display:block;
}
.menu .active, .menu li:hover {
background-position: 0 -34px;
float: left;
}

.menu .active span, .menu span:hover {
background-position: 100% -34px;
float: left;
line-height: 30px;
height: 30px;
}

.menu li a span {
font: 12px/31px Tahoma;
font-weight: 700;
display: block;
text-align: center;
padding: 0 8px 0 9px;
}
.menusep {
background-position: 0 0;
height: 32px;
line-height: 32px;
width: 2px;
}
.promenu_sub {
background-repeat: repeat-x;
background-image: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnKM2Ya1fS8jDJ1HHdMI-ezhNkb9m-oKRN4glyuVNxpSSX8x8RPUjvJDMotW8kK4ESgVrRDoyorwAMALFVwjg1SA8qjFlEwN5k-d8fQX4X8PuBZKW_1DDbx-8rr2fKLFypGroAQZSOpNB0/s240/1-6-bg_sub2.png);
margin-top:1px;
padding:1px;
color: #fff;;
margin: 1 1px;
padding: 7px 12px;
font-weight:bold;
font-family:Arial, Helvetica, sans-serif;
font-size:12px;
cursor:pointer;
border-top:1px solid #ededed;
}
</style>

<script type="text/javascript">
//<![CDATA[
var style1=["0px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjB_qrIHJUU5DQRcfgQl-YIKYjohct7cYf8ewjXcHSs7c6vLXRzb4t6evkEdJua2s3d5iEc4guYOFXO7esOY3WSgytkmknf27qz9fM3g-uEx_jQEUGhs9CvXYfsEyYuq7cn2akK_HFoVPw_/s318/style1-sp.png"];

var style2=["-40px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTqOntwRQqU9VF4J4kkWSIZzSNXAEhXiueSdBP-gIuyvquFyXdh9d5H1JC1PosFcAWXS9KNI10fyCd45BnV5yjxlkAF02vrGU_ZRdZrmseQ42s6j7Ta1nhD0rxgp3x_lYLKQBtRNj6mUY9/s318/style2-sp.png"];

var style3=["-80px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtTN6BHISwwbYMZNQObYcA5GeFdVjTINiROvkhR6yY02WbllPgX5AYjuByBmdHkVNcaF9B0jmjaOZcIYPIArTdTeNJ3CHaFyyJ2UTt9yumUtFs0_eMmaQS0k9MrKqGa6ux9D8LFgUf0k9S/s318/style3-sp.png"];

var style4=["-120px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiq73_C2ve2QYjG_-c21IILozgLXRb5LHXBm4UvxrY9zOCl4je5UYDR4O2fPbxCp0XkWurDEbB_mLctSCIWwZRVL68oChb0aRtQ2m08zI6zUvi3-Bhcj_SZKaEE9gtClxNoxeLEM9DM6d0H/s318/style4-sp.png"];

var style5=["-160px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJGlExjcgENp_Rl0bb6kt1riavFd_Xi746JnRp7rRmpXa2ElH_6lKuDfYNCei6v9PpQoEBh7zSj7-ypAWh1lGb1qJFoRYDj-c3Bhn3dmWVa13vYUrCpnLGtrrI4kxJbVkjHGKgvRBEAMr8/s318/style5-sp.png"];

var style6=["-200px","https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieunm61x5Wmj0NBUJI_hGueTyIK_b3_5ZxAlcpeBMofbkgMFlQ1OrUA6DqbLHbAvvKdx5-DOinVWEBvotu9uo8yjFsi6u4DYAHRCn9su0SRUReFzhciuUubZUZHD5PnX1DKjbyXX3DtVsy/s318/style6-sp.png"];

var m0=["http://demo.fandung.com","Home","home_id"];
var m1=["/search/label/Label1","Label1","id1"];
var m2=["/search/label/Label2","Label2","id2"];
var m3=["/search/label/Label3","Label3","id3"];
var m4=["/search/label/Label4","Label4","id4"];
var m5=["/search/label/Label5","Label5","id5"];
var m6=["/search/label/Label6","Label6","id6"];
var m7=["/search/label/Label7","Label7","id7"];
var m8=["/search/label/Label8","Label8","id8"];
var m9=["/search/label/Label9","Label9","id9"];

var ttmenu = [m0,m1,m2,m3,m4,m5,m6,m7,m8,m9];
var n=ttmenu.length;
var list_menu=[];
var list_active=[];
var c_menu='';
var m_homepage='http://demo.fandung.com/';
var h_cond=m_homepage+'search';

var active_cond = location.href;
var home_cond=active_cond.split("?")[0];

var bg_pos='';
var sp_img='';

if (active_cond.indexOf(ttmenu[1][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else if (active_cond.indexOf(ttmenu[2][0])!=-1) {bg_pos=style3[0];sp_img=style3[1];}
else if (active_cond.indexOf(ttmenu[3][0])!=-1) {bg_pos=style4[0];sp_img=style4[1];}
else if (active_cond.indexOf(ttmenu[4][0])!=-1) {bg_pos=style5[0];sp_img=style5[1];}
else if (active_cond.indexOf(ttmenu[5][0])!=-1) {bg_pos=style6[0];sp_img=style6[1];}
else if (active_cond.indexOf(ttmenu[6][0])!=-1) {bg_pos=style2[0];sp_img=style2[1];}
else if (active_cond.indexOf(ttmenu[7][0])!=-1) {bg_pos=style3[0];sp_img=style3[1];}
else {bg_pos=style1[0];sp_img=style1[1];}

var mcss = document.createElement('style');
mcss.type = 'text/css';
mcss.innerHTML = '#promenu, .promenu_sub {background-position: 0 '+bg_pos+'} .menu li:hover, .menu .active, .menu .active span, .menusep, .menu span:hover {background-image: url('+sp_img+')}';
document.body.appendChild(mcss);

// submenu
function otab(){document.write('<table border="0" bordercolor="#999" style="background-color: #FFF" cellspacing="0" cellpadding="0">');}

function submn(submn_link,submn_text){ document.write('<tr><td onmouseover="this.style.background=\'url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivgYcRSBwnBNiJsle55vTMn4n0k-1ppAyaKCOT0II51ZkSAcqRZQOJd8vdAxdoRPTOXbdDoqSkbtp3MSgdEhguVP9prLASoyzqCHf3J0NbJSl8tJW3cA2F4qKBfzayhOB1uKe2TEZhyphenhypheneql/s400/1-6-bg.png) repeat-x 0 '+bg_pos+'\'" onmouseout="this.style.background=\'url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgnKM2Ya1fS8jDJ1HHdMI-ezhNkb9m-oKRN4glyuVNxpSSX8x8RPUjvJDMotW8kK4ESgVrRDoyorwAMALFVwjg1SA8qjFlEwN5k-d8fQX4X8PuBZKW_1DDbx-8rr2fKLFypGroAQZSOpNB0/s240/1-6-bg_sub2.png) repeat-x 0 '+bg_pos+'\'" class="promenu_sub" onclick="window.location.href=\''+submn_link+'\'">'+submn_text+'</td></tr>');}

function ctab(){document.write('<\/table>');}

function otab(child_id){document.write('<table id="' + child_id +'" border="0" bordercolor="#999" style="background-color: #FFF" cellspacing="0" cellpadding="0">');}

//submenu

c_menu +='<div id="promenu"><ul class="menu">';

for (var i=0;i<n;i++) {

if ((home_cond==m_homepage)||(home_cond==h_cond)) {list_active[0]='class="active"';}
else if (active_cond.indexOf(ttmenu[i][0])!=-1) {list_active[i]='class="active"';} else {list_active[i]='';}

list_menu[i]='<li><a href="'+ttmenu[i][0]+'" '+list_active[i]+' id="'+ttmenu[i][2]+'"><span>'+ttmenu[i][1]+'</span></a></li>';
c_menu +=list_menu[i];
if (i<n-1) {c_menu +='<li class="menusep"> </li>';}
}
c_menu +='</ul></div>';
document.write(c_menu);

//]]>
</script>

<script type="text/javascript">
otab("id1_child");
submn('link_submenu1.1','Submenu 1.1');
submn('link_submenu1.2','Submenu 1.2');
ctab();
at_attach("id1", "id1_child", "hover", "y", "pointer");

otab("id2_child");
submn('link_submenu2.1','Submenu 2.1');
ctab();
at_attach("id2", "id2_child", "hover", "y", "pointer");

otab("id3_child");
submn('link_submenu3.1','Submenu 3.1');
ctab();
at_attach("id3", "id3_child", "hover", "y", "pointer");

otab("id4_child");
submn('link_submenu4.1','Submenu 4.1');
submn('link_submenu4.2','Submenu 4.2');
submn('link_submenu4.3','Submenu 4.3');
submn('link_submenu4.4','Submenu 4.4');
submn('link_submenu4.5','Submenu 4.5');
submn('link_submenu4.6','Submenu 4.6');
ctab();
at_attach("id4", "id4_child", "hover", "y", "pointer");

otab("id5_child");
submn('link_submenu5.1','Submenu 5.1');
submn('link_submenu5.2','Submenu 5.2');
ctab();
at_attach("id5", "id5_child", "hover", "y", "pointer");

otab("id6_child");
submn('link_submenu6.1','Submenu 6.1');
ctab();
at_attach("id6", "id6_child", "hover", "y", "pointer");

otab("id7_child");
submn('link_submenu7.1','Submenu 7.1');
submn('link_submenu7.2','Submenu 7.2');
submn('link_submenu7.3','Submenu 7.3');
ctab();
at_attach("id7", "id7_child", "hover", "y", "pointer");

otab("id8_child");
submn('link_submenu8.1','Submenu 8.1');
submn('link_submenu8.2','Submenu 8.2');
submn('link_submenu8.3','Submenu 8.3');
submn('link_submenu8.4','Submenu 8.4');
submn('link_submenu8.5','Submenu 8.5');
submn('link_submenu8.6','Submenu 8.6');
submn('link_submenu8.7','Submenu 8.7');
ctab();
at_attach("id8", "id8_child", "hover", "y", "pointer");
</script>
- bản updated vẫn chưa hoàn thiện ở phần hiện hiệu ứng thay đổi style cho menu ở các trang submenu, mình sẽ fix sớm khi có thể.

Popular Posts