This article is the article which is being introduced in Japanese about a way of dynamic addition of web parts.
Webパーツとは、ユーザーがWeb上でニュースやカレンダーなどの様々なパーツを自由にレイアウトできる仕組みで、Windows Live や My Yahoo! などのページを見ていただければイメージしやすいかと思います。Microsoft 製品ではポータルサイトの制作として SharePoint なんかが有名ですね。
このWebパーツを使ったサイトの制作は ASP.NET(2.0) でも行うことができ、ツールボックスにはWebパーツ関連のコントロールがいくつか存在します。10個ちょっとしかありませんが、サーバーコントロールであればほとんどがWebパーツ化できるので意外といろんなものが作れたりします。
作り方も非常に簡単で、コントロールを配置するだけでできてしまいます。「レイアウトの位置変更」や「最小化」「非表示」「名前変更」などは初めから備わっており、ユーザーごとにレイアウトの状態保存も自動的に行ってくれます(まあ実際に運用となるとそれなりに考慮しないといけない部分もありますが)。
で、やっと本題です(^^;)
ユーザーが扱えるWebパーツはあらかじめ配置しておいたコントロールのみとなってしまいますが、場合によっては新しいコントロールを動的に追加したいこともあるかと思います。
それを行うには下のようなコードを追加することで動的にWebパーツを追加することができます。下のコードではサーバーコントロールである TextBox を Webパーツでラップし、Webパーツゾーンに追加しています。(Webパーツはゾーンの上に配置する形になります。もちろん移動もです。)
protected void Page_Load(object sender, EventArgs e)
{
TextBox textBox = new TextBox();
textBox.ID = "addedTextBox";
textBox.Attributes.Add("runat", "server");
GenericWebPart gwp = this.WebPartManager1.CreateWebPart(textBox);
gwp.Title = "追加したテキストボックス";
this.WebPartManager1.AddWebPart(gwp, this.WebPartZone2, 0);
}
しかし、実行してみるとわかりますが、ボタンなどを押してポストバックするたびにテキストボックスがどんどん追加されていってしまいます。これはWebパーツのレイアウト情報がポストバックするたびに保存されるため、Page_Load メソッドが呼ばれるたびにWebパーツを追加してしまうからです。
なので普通は「IsPostBack」プロパティでポストバックの時はWebパーツ追加を実行しないようにします。
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
TextBox textBox = new TextBox();
textBox.ID = "addedTextBox";
textBox.Attributes.Add("runat", "server");
GenericWebPart gwp = this.WebPartManager1.CreateWebPart(textBox);
gwp.Title = "追加したテキストボックス";
this.WebPartManager1.AddWebPart(gwp, this.WebPartZone2, 0);
}
}
[注意]
上を検証する前に以下のコードを実行して以前のWebパーツ情報を消してください。
for (int i = this.WebPartManager1.WebParts.Count - 1; i >= 0; i--)
{
try
{
this.WebPartManager1.DeleteWebPart(this.WebPartManager1.WebParts[i]);
}
catch (Exception) {}
}
※try-catch でスルーしているのは静的Webパーツが存在することを考慮して。
で、こうすることによってページが開かれたときにだけWebパーツが追加されるようになるのですが、実は問題があり、一回ポストバックをかけるとなぜか追加したWebパーツが消えてしまいます。おそらく Page_Load メソッドで追加したWebパーツは WebPartManager が保存管理していないためではないかと思われます。ちなみにボタンイベントなどで追加した場合は正常に保存されるようです。
この問題を解決するためにいろいろ探し回ったのですが、日本語のサイトでは見つけられず、英語圏の方でようやく見つけられました。
http://blogs.neudesic.com/blogs/david_barkol/archive/2006/02/10/45.aspx
簡単に訳すと「追加した Webパーツをパーソナル化するために変更フラグを立てなければいけない」ということです。
このフラグを立てるには「WebPartManager.SetPersonalizationDirty」というメソッドを呼ぶことにより設定できるのですが、実はこのメソッドは protected で宣言されており、そのままでは呼ぶことができません。上記リンクではこのクラスを派生させて呼び出すようにしています。
まず、このクラスを派生させたクラスを作るのですが、サーバーコントロールは単純に継承したクラスのコードを記述してもそのままではページに配置できません。必ずクラスライブラリ専用のプロジェクトを作成し、そちらに記述する必要があります。
ソリューションからプロジェクトを作成します。
プロジェクトの名前は適当です。
System.Web の参照を追加しましょう。
WebPartManager の派生クラスを作ります。今回クラス名は「WebPartManagerEx」としておきます。コードは下のようにして基本クラスの「SetPersonalizationDirty」メソッドを呼べるようにします。
using System;
using System.Data;
using System.ComponentModel;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace ClassLibrary1
{
[ToolboxData("<{0}:WebPartManagerEx runat=server>")]
public class WebPartManagerEx : WebPartManager
{
public new void SetPersonalizationDirty()
{
base.SetPersonalizationDirty();
}
}
}
クラスを作成したらページに貼り付けます。前の WebPartManager は消すなり何なりしてください。
あとはプログラムで Webパーツを追加した後にこのメソッドを呼び出せばOKです。
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
TextBox textBox = new TextBox();
textBox.ID = "addedTextBox";
textBox.Attributes.Add("runat", "server");
GenericWebPart gwp = this.WebPartManagerEx1.CreateWebPart(textBox);
gwp.Title = "追加したテキストボックス";
this.WebPartManagerEx1.AddWebPart(gwp, this.WebPartZone2, 0);
this.WebPartManagerEx1.SetPersonalizationDirty();
}
}
まあ、これが正当なやりかたなのかどうかはわかりませんが、こういうやり方もあるんだ程度に知っておいていただければと思います。
ちなみにこの Webパーツをうまく使えば下のようなポータルサイトを作ることだってできます。視覚的にレイアウトの調整ができるのでウマー(゚Д゚)です。