2007年9月27日木曜日

プロパティでSystem.Typeを指定したい?(5)

今回はConvertTo関係。これでTypeConverterな話は一段落。前々の記事に書いたように「TypeConverterで扱う型・クラスをある型・クラスに変換」な処理を行うのがこのメソッド。

使われる場面ってのはプロパティダイアログ・・・ではなく実行時だと思っていていいのかな?いきなりだけど実際のロジック。

''' <summary>コンバータが、指定したコンテキストを使用して、指定した型にオブジェクトを変換できるかどうかを示す値を返します</summary>
Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
    If destinationType Is GetType(System.Type) Then Return True
    Return MyBase.CanConvertTo(context, destinationType)
End Function

''' <summary>指定したコンテキストとカルチャ情報を使用して、指定した値オブジェクトを、指定した型に変換します</summary>
Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
    If TypeOf value Is System.Type Then
        For Each child As System.Type In Me.GetClassList(context)
            Dim targetType As System.Type = TryCast(value, System.Type)
            If (targetType IsNot Nothing) AndAlso (child.Name = targetType.Name) Then
                Dim result As String = String.Empty
                result = child.Name.Replace(child.Namespace, "")
                Return result
            End If
        Next
        Return String.Empty
    End If
    Return MyBase.ConvertTo(context, culture, value, destinationType)
End Function

プロパティに設定されるのはStringによるクラス名なので、それを実際のインスタンス(System.Type)から変換するのが主なお仕事になるね。その際に注意するのが、クラス一覧は名前空間を除去した形で利用したほうがいいということ。そりゃー、ダイアログのあの小さい領域にフルフル名称で表示されていたら見づらいのでね。

とりあえずここまで準備できればあとは実際のプロパティ側で属性を指定すればOK。
そこは次回で。

VB6サポート

昨日Windows2008Serverのセミナーに参加してきたんだけど、どうやらまだVB6ランタイムはサポートされる様子・・・。

技術者としては「とっととサポートうち切って.Netに完全移行」させてしまっていいと思うんだよねぇ。どうせVB6がサポートされないと困る!なんて言っている人たちって、サポートうち切られたからって他OSに移る可能性は低いと思うんだよなぁ。

WindowsでVBしか使えない人だから文句を言っているような気がしてならないのよ。

2007年9月26日水曜日

ServerCoreの判断

今日のセミナーネタ。

Dim bServerCore
Const strExplorer = "\explorer.exe"
strRoot = WshEnv("SYSTEMROOT")
strExpPath = strRoot + strExplorer

Set objFso = CreateObject("Scripting.FileSystemObject")

If objFso.FileExists(strExpPath) Then
    WScript.Quit(0)
    bServerCore = False
Else
    bServerCore = True
End If

マイクロソフトのセミナーでこんな力技の紹介されるとは思わなかったw

2007年9月21日金曜日

PowerShellスゲー

PowerShellだと、データベースに接続してどうのこうのするスクリプトも.Netな感覚そのままに記述ができるそうで。元ネタから引用。

$connection = New-Object System.Data.SqlClient.SqlConnection("Data Source=.; Integrated Security=True");
$command = New-Object System.Data.SqlClient.SqlCommand("select getdate() as [date]", $connection);
$connection.Open()
$value = $command.ExecuteScalar();
$connection.Close();

バッチスクリプトだけでとんでもなくいろんなことができてしまうんだねぇ。月次更新とか一括更新系はもうAPにする必要すらないのかも・・・。

2007年9月20日木曜日

現在ログインしているユーザーの判断

あぁ、頭が固くなってきているようで・・・。
どのAPIとかクラス使ってできるのかなぁ・・・なんて考えていたけど、一番簡単な手段があった。

コマンドプロンプトでSETを実行 USERNAME変数を確認

なんでもかんでも自前でやろうとしてしまうのは、よくないクセだなぁ。

2007年9月19日水曜日

プロパティでSystem.Typeを指定したい?(4)

今回はConvertFrom関係。前の記事で書いたように「ある型・クラスから、このTypeConverterで扱う型・クラスに変換」にちなんだ処理を行うのがこのメソッド。

プロパティダイアログで利用する場合は「String ←→ System.Type」と変換できればよいので、CanConvertFromメソッドでは、ある型=StringであればTrueを返却するようにコーディングすればOK。

''' <summary>指定したコンテキストを使用して、コンバータが特定の型のオブジェクトをコンバータの型に変換できるかどうかを示す値を返します</summary>
Public Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
    If sourceType Is GetType(String) Then Return True
    Return MyBase.CanConvertFrom(context, sourceType)
End Function

こんな感じで大丈夫。そしてConvertFromメソッドでは実際の変換処理を記述してあげればOK。内部でプロパティでSystem.Typeを指定したい?(3)の時に書いたロジックを使うのでそこも参照してください。

''' <summary>指定したコンテキストとカルチャ情報を使用して、指定したオブジェクトをコンバータの型に変換します</summary>
Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
    If TypeOf value Is String Then
        For Each child As System.Type In Me.GetClassList(context)
            If child.Name.Replace(child.Namespace, "") = value.ToString Then
                Dim result As Object = Nothing
                result = System.Activator.CreateInstance(child)
                Return result.GetType
            End If
        Next
        Return Nothing
    End If
    Return MyBase.ConvertFrom(context, culture, value)
End Function

こんな具合かな?利用できるクラスの一覧を取得して、一覧に存在するクラスが指定された際にはそのTypeを返却する。一覧に存在しない際はNullを返却する。返却する際には一覧で保持しているクラス名から、実際のインスタンスを生成(System.Activator.CreateInstance)して、そのTypeを取得しています。

とまあ、こんな感じでFrom関係のメソッドも大したことがないのよね。To関係のメソッドもそれほど違いはないので、次回の記事と見比べてもらうといいかも。

外人さんのやる気

日本人と外国人との差に、「他の国の言葉でも躊躇無く突撃してくる」というのがある気がする。

MSDNフォーラムでそれっぽいスレッドがあったんだよねぇ。
恐らくは機械翻訳かけて投稿したんだろうけど・・・。
自分はそこまでできないなぁ・・・。やるなら自力で文章起こさないと気がすまない。

重みのない文字

SqlServerのMVPな人の記事から。

今まで全く知らなかった、こういった「重みの無い文字」。U+32C0とかその類の文字なんて、この記事みるまで存在すら知らなかったw

んで、記事ではPowerShellでのソート結果となっているんだけど、なんか雰囲気は.Net全般の様な気がするんだよねぇ・・・。SqlServerだとソートの照合順序指定に関わるからまだ判るけど、シンプルに「文字のソート」全体の話題だとしたら結構面白い問題かも。

まぁよほどアレな人じゃないとこんな文字打たないだろうけど。

2007年9月15日土曜日

ヘッダの拡張・・・どうするよ

DataGridView本体は色々拡張しているんだけど、未だに拡張を行っていない部分があるんだよねぇ。ヘッダなところが。

どうデザインさせるか」とかを考えると、エディタ作れるようにならなければ厳しい部分があるんだよねぇ。自分が使うだけならどうとでもやるんだけど、他の人が使うのが大前提だからなぁ。難しいわぁ。

2007年9月13日木曜日

プロパティでSystem.Typeを指定したい?(3)

前回書いた6つのメソッドの中で、まずはGetStandardValuesメソッドとGetStandardValuesSupportedメソッドから。

「標準値リスト」を提供する部分に関わるメソッドで、IDE側ではGetStandardValuesSupportedメソッドがTrueを返した際に、GetStandardValuesメソッドでドロップダウンリストに表示する一覧を取得しにくる、という流れになっているのよ。

んじゃ今回提供したい標準値リストって何が入っていればいいか?と言われると。

「自分のプロジェクトと参照設定されているアセンブリなどに存在するクラスの一覧」

が目標になるわけだ。同じようなものとしては、DataGridViewのカラムを設定するあたりだね。あれと同じ動き。とりあえず説明はあとで大体のロジックを。

''' <summary>設定対象となるクラスの一覧を取得します</summary>
Protected Function GetClassList(ByVal context As System.ComponentModel.ITypeDescriptorContext) As ArrayList
    Dim service As ComponentModel.Design.IDesignerHost _
        = DirectCast(context.GetService(GetType(ComponentModel.Design.IDesignerHost)), ComponentModel.Design.IDesignerHost)
    Dim discoveryService As ComponentModel.Design.ITypeDiscoveryService _
        = DirectCast(service.GetService(GetType(ComponentModel.Design.ITypeDiscoveryService)), ComponentModel.Design.ITypeDiscoveryService)

    Dim result As New ArrayList
    For Each childtype As System.Type In Me.FilterGenericTypes(discoveryService.GetTypes(Me._targetType, False))
        If ((childtype Is Me._targetType) OrElse childtype.IsAbstract) OrElse _
            (Not childtype.IsPublic AndAlso Not childtype.IsNestedPublic) Then Continue For

        Dim attribute As DataGridViewColumnDesignTimeVisibleAttribute _
            = TryCast(ComponentModel.TypeDescriptor.GetAttributes(childtype).Item(GetType(DataGridViewColumnDesignTimeVisibleAttribute)), DataGridViewColumnDesignTimeVisibleAttribute)

        If (attribute Is Nothing) OrElse attribute.Visible Then result.Add(childtype)
    Next
    Return result
End Function

''' <summary>インスタンス化できないクラスを一覧から除去します</summary>
Private Function FilterGenericTypes(ByVal childTypes As ICollection) As ICollection
    If (childTypes Is Nothing) OrElse _
       (childTypes.Count = 0) Then Return childTypes

    Dim childArray As New ArrayList(childTypes.Count)
    For Each type As System.Type In childTypes
        If Not type.ContainsGenericParameters Then childArray.Add(type)
    Next
    Return childArray
End Function

中々お目にかからないクラスが多いと思う。IDesignerHostとかITypeDiscoveryServiceとか。ざっくりと言ってしまうと、ITypeDiscoveryServiceインターフェースは「プロジェクトと参照設定されているアセンブリ」に対して探し物をしてくれるクラスが継承しているもの。IDesignerHostインターフェースはMSDNあたりで見てくださいw
なのでITypeDiscoveryServiceを利用してクラス一覧を取得、んでその中からインスタンス化できないクラスを除去。もう一つついでにデザイン時に利用していいクラスかどうか(DataGridViewColumnDesignTimeVisibleAttribute)を判断して一覧を整理してあげればOK。ただ、最後の属性を見てってのは特にやらなくても問題ないケースが多いかな。デザイン時には非表示だけど実行時には使いたいようなクラスを自前で用意するような、そんな面白いケースになった時にこの属性を使ってあげていればいいんだけどね。今のところであってません、そんなケースにはw

これで標準値リストについては大体OK。このロジックは、コントロールを拡張しだすと結構使いたい場面が多くなる・・・人もいるかな?

曜日名を全て取得する

昔:Constなどで定義、またはマジックナンバーとして焼付け
今:System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.DayNamesでString配列として取得

色々考えるとSystem.Globalization関係って使っていたほうが楽になりそう。和暦→西暦変換とか西暦→和暦変換もOSの機能で用意されているものを利用できるんだよね、こっちだと。

Cultureの中でCalendarクラスのインスタンスを持っているんだけど、こいつが色々用意されているのよ。和暦用にJapaneseCalendarクラスとかもあるんで。

ここらへんを使うと日付系のロジックは結構シンプルにいけるのかも・・・?

2007年9月12日水曜日

ISO8601

1週間というものを定義しているISOがあるなんて知りませでしたよ、ええ。

こいつに基くと、最初の週は「1 年の最初の週が、週の最初の曜日として指定されている曜日が次に訪れるまでに 4 日以上かかる週になるように指定します。」なんてルールに基くので、1月2日が土曜日なんて場合は、1/1~1/2は「前年度の最終の週」という扱いにするそうだ。

第x週の扱いを調べているとこんなのが出てきて少々驚き。
・・・ま、使わないんだろうなぁw

プロパティでSystem.Typeを指定したい?(2)

プロパティでTypeConverterを利用したデザイン時サポートを行う際には、System.ComponentModel.TypeConverterクラスを継承して実装していくのが楽なんだと思う。まぁ実際には大した作業量の違いはないかも知れないけど。で、このクラスのメソッドで実装する必要になるのは次にあげるものくらい。

  • GetStandardValuesSupportedメソッド
  • GetStandardValuesメソッド
  • CanConvertFromメソッド
  • CanConvertToメソッド
  • ConvertFromメソッド
  • ConvertToメソッド

上にあげた6つのメソッドは、実際のところ2つ*3組という考え方で捉えるといいかも。

まずはGetStandardValuesSupportedメソッドとGetStandardValuesメソッド。これはプロパティダイアログでドロップダウン型の時に関わってくるメソッドで、「標準値リスト」というのをサポートするかどうか、という関連で利用されるね。GetStandardValuesSupportedメソッドでTrueを返却するなら標準値リストをサポートする、Falseならサポートしないという風にIDE側で判断するのに使われます。ドロップダウン型は標準値リストをサポートする時だけ利用できると思っていればいいかな。そしてそのリストに設定される値一覧がGetStandardValuesメソッドで取得できる、と。

そしてCanConvertFromメソッドとConvertFromメソッド。これは「ある型・クラスから、このTypeConverterで扱う型・クラスに変換」という処理に関連している。変換可能な場合はCanConvertFromメソッドでTrueを返却して、実際の変換処理をConvertFromメソッド内部で行うことになるね。今回みたいなケースでは「文字列からSystem.Type」という変換をすればよし。

それと逆の動作をするのがCanConvertToメソッドとConvertToメソッド。こっちは「TypeConverterで扱う型・クラスをある型・クラスに変換」という処理を行うことになる。変換可能であればCanConvertToメソッドでTrueを返却、実際の処理をConvertToメソッドで行えばよし。実際にはConvertFrom程処理は少なくないんだよね、こっち。

細かい中身については次回以降で。

2007年9月8日土曜日

プログラムのセルフアップデート

Vista以降のOSで一番問題になるのがこれかも。

今回のシステムでも(一応)は機能として最新モジュールのコピーやらそういった機能を持たせているんだけど、適切に設定されているセキュリティ、具体的に言うとProgramFilesフォルダ関連にセットアップしだ場合は、セルフアップデート機能は「自分自身」以外は失敗してしまうことになるね。
セキュリティ的な方面から言うと、

ProgramFilesはユーザー領域ではなく、システム領域なので勝手に更新されるのはよくない

という事なんだよな。気持ちはわかる。管理者でも読み取りと実行権限しか持たせないってのはよくわかる。実際に対応するとなるとWindowsInstallerかClickOnceなどの純正テクノロジを利用するのがベスト。VistaやWindows2008Serverなどで用意されている救済策を利用して、セットアップ系プログラムと誤認させる手段もあることはあるけど、かなり後ろ向きというかいきあたりばったり。

そう考えると次の3通りになるのかなぁ?

  1. システムのAP全てに自己更新機能を持たせる(ClickOnce)
  2. 特定のタイミングでシステム全体のAPをインストールするmsiを作成し、ActiveDirectoryなどで配布
  3. ProgramFilesにインストールさせない(w

技術屋としては1か2だけど・・・。多分会社としては3を選択しそうな気がしてならないw

2007年9月7日金曜日

プロパティでSystem.Typeを指定したい?

あるクラスのプロパティでSystem.Typeなものを指定したい場合というのは結構あるだろうし、普通にロジックで

Public Property DummyType As System.Type
End Property

なんて実装してしまえばできるから使っている人は多いと思う。で、今回の伝票レイアウトな話題でも、それを利用しているんだけど単純にロジック書くだけではちょっと問題があるんですな、DataGridView関連では。

それは「InitializeComponentメソッドで処理されないプロパティは、明示的に対応処理を行わないといけない」という問題。DataGridViewで有名どころはデザイン時のRowTemplate関連かな?こいつはデザイン時にプロパティを設定しても、デザイン画面では即座に反映されてくれない。一度表示されている行を消して、その後に戻してあげないといけないんだよね。仕様としてRowTemplate関連は「行を追加する」タイミングでしか適用されないので、仕様通りっちゃーその通りなんですな。
これと似ていて、ある種のプロパティはInitializeComponent内部で操作されていないと、実装者がロジックでゴリゴリ対応してもらわないといけない状態になるんだよね。そして普通にSystem.Typeなプロパティは標準で設定するダイアログといういのがないのでInitializeComponent内部で設定してもらうことができない → 実装者がゴリゴリ、となってしまうわけだ。こりゃあ使いづらい。というかバグ出してくれそうw

ではどうするか、というとSystem.ComponentModel.TypeConverterというクラスを利用することになります。これは「変換」を行うためのクラスで、「文字列→インスタンス」とか「インスタンス→文字列」なんてことをするために利用するね。今回ではプロパティダイアログ上で、「クラスを選択→クラス名を文字列で」と「クラス名→System.Type」という機能が必要なので用意することにします。

詳しい話は次回以降。

これからの文字項目の桁数

今のところデータベースの文字型項目で容量を考える際、「その項目に必要なバイト数」で領域を定義していると思うんだけど、今後を考えるとこのやり方では非常にマズイというか。

CHARやVARCHAR世代は主にバイト数でカウントする世代で文字コードなんてものは全然考えていない場合が多い。文字コードを考え出して、「バイト数で定義なんてできねーよ」となってでてきたNCHARやNVARCHAR世代は、文字数でカウントする世代だね。後者の「文字数で定義」になれている人ならサロゲートペアがこようと基本は問題ない。
(内部的にバイト数を利用していたりしなければ)

そしてもう一つの問題がVistaより可能になった「UniCode文字結合」。サロゲートペアは「補助として次の16bitを利用する」という1文字=32bitな特殊字のことで、文字結合というのは「が」と「か + ゛」は同一に扱うという問題。本当に厄介なのはこっちかも知れない。何しろVistaからなんだから。

対応としては、文字列を扱うときにはカルチャを意識してロジック組まないといけないってことだそうで。文字を結合するのは現状と同じでいいけど、比較するときにカルチャを指定するってこと。これを踏まえると文字列比較用のメソッドを、今のうちに用意しておいたほうがいいってことなんだよねぇ。

ちなみに文字結合はSqlServer2005では対応されている。「が」という文字を検索したとき、「か + ゛」もちゃんと条件にひっかかってくれる。

・・・あれ?てことはNCHARって固定長じゃないの?それとも2文字分使うってことなのか??

2007年9月4日火曜日

シマンテックやるねェ!

サーバープログラムをアンインストールするとプログラムフォルダの内容が削除される

シマンテックが久々に放ったホームランw
いやぁ、こりゃ豪快すぎる。

伝票レイアウトセル(5) セル その3

Paintメソッドでイメージを表示させるにあたってもう一つ考えておく必要がある話題が。

というのも、このPaintメソッド自体はやたらめったらに呼ばれるから。
「マウスを動かす」「キーを押す」などDataGridViewでちょっとでも変更が起きそうな際には必ず呼ばれる。
なのでそのあたりを考慮しておかないと、もともとコストの高いイメージキャプチャ処理なので重いAPとなってしまうわけだ。

んじゃ何をもって判断してあげればよいかというと。

  • DataGridViewElementStates
  • DataGridViewCellStyle

この二つ。DataGridViewElementStatesは引数elementstateに設定されて、DataGridViewCellStyleは引数cellstyleに設定されてくる。これらの中身をセルレベルで保持させておいて、Paintメソッドの都度引数と保持したものとを比較し、異なる際に限ってイメージのキャプチャと描写処理を行ってあげるのがベターだと思う。
・・・多分もう少し適切な方法もあるとは思うんだけど。

ちなみにelementstateはEnumなのでそのまま保持だけど、cellstyleはClassなので、cellstyle.Cloneの結果を保持するようにしておく必要があるね。そうしないと引数と保持しているものが同じものを参照してしまう。

''' <summary>現在の DataGridViewCell を描画します。</summary>
Protected Overrides Sub Paint(ByVal graphics As System.Drawing.Graphics, _
                              ByVal clipBounds As System.Drawing.Rectangle, _
                              ByVal cellBounds As System.Drawing.Rectangle, _
                              ByVal rowIndex As Integer, _
                              ByVal elementState As System.Windows.Forms.DataGridViewElementStates, _
                              ByVal value As Object, _
                              ByVal formattedValue As Object, _
                              ByVal errorText As String, _
                              ByVal cellStyle As System.Windows.Forms.DataGridViewCellStyle, _
                              ByVal advancedBorderStyle As System.Windows.Forms.DataGridViewAdvancedBorderStyle, _
                              ByVal paintParts As System.Windows.Forms.DataGridViewPaintParts)

    MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)

    If Me._editorInstanceType Is Nothing Then Return
    If (_beforeState <> elementState) OrElse _
       (_beforeCellStyle Is Nothing) OrElse _
       (Not _beforeCellStyle.ToString = cellStyle.ToString) Then

        Using subEditor As MultiLayoutControl _
            = TryCast(System.Activator.CreateInstance(Me._editorInstanceType), MultiLayoutControl)

            If subEditor Is Nothing Then Return

            subEditor.Top = subEditor.Height * -1
            subEditor.Left = subEditor.Width * -1
            If (Me.DataGridView IsNot Nothing) AndAlso _
               (Me.DataGridView.TopLevelControl IsNot Nothing) Then
                Me.DataGridView.TopLevelControl.SuspendLayout()
                Me.DataGridView.TopLevelControl.Controls.Add(subEditor)
            End If

            '色とフォントの設定
            If (elementState And DataGridViewElementStates.Selected) = DataGridViewElementStates.Selected Then
                subEditor.BackColor = cellStyle.SelectionBackColor
                subEditor.ForeColor = cellStyle.SelectionForeColor
            Else
                subEditor.BackColor = cellStyle.BackColor
                subEditor.ForeColor = cellStyle.ForeColor
            End If
            subEditor.Font = cellStyle.Font

            subEditor.Values = TryCast(value, SubEditor.SubEditorValues)
            subEditor.OnConvertValuesToInterface()
            'イメージキャプチャのメソッドを呼び出す
            _controlImage = subEditor.CreateControlImage(False)

            If (Me.DataGridView IsNot Nothing) AndAlso _
               (Me.DataGridView.TopLevelControl IsNot Nothing) Then
                Me.DataGridView.TopLevelControl.Controls.Remove(subEditor)
                Me.DataGridView.TopLevelControl.ResumeLayout()
            End If

        End Using
        _beforeState = elementState
        _beforeCellStyle = cellStyle.Clone
    End If

    'イメージの描写
    graphics.DrawImage(_controlImage, cellBounds.X, cellBounds.Y, _controlImage.Width, _controlImage.Height)
    '枠線が必要な際は描写
    If (paintParts And DataGridViewPaintParts.Border) = DataGridViewPaintParts.Border Then
        MyBase.PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle)
    End If
End Sub

ロジックとしてはこんな感じかと。ここまでの記事で用意した拡張メソッドやらなんやらてんこもりなので、昔の記事も色々と見てください。

2007年9月3日月曜日

伝票レイアウトセル(5) セル その2

今回はセルクラスの一番のポイント。Paintメソッド。

このメソッドでやることってのは単純で、「DataGridViewの必要な領域にイメージを描写する」ということ。
テキスト系のセルであれば、設定されている値とかを画面に描写するし、イメージ系であればそのままグラフィックを描写してあげればいいんだよね。

ただこの伝票レイアウトセルの場合、表示したいイメージというのはセル側からではわからないのよ。
(実際には編集コントロールにて生成されるユーザーコントロールのイメージを描写したいから)

そのためDataGridView.Paintメソッドでは「編集コントロールでホストするコントロールのインスタンスを生成、値を展開後イメージをキャプチャする」という処理が必要になるね。どうみても重い処理になるのは間違いないところだw

ユーザーコントロールその2で書いてあるイメージのキャプチャを利用することになりやす。
こいつを「値の展開後」に呼び出してあげてキャプチャ、その次にgraphics.DrawImageメソッドで画面に表示というか描写というか。ユーザーコントロールその1で定義した、「値をコントロールへ」と「コントロールから値へ」というイベントはここで呼び出すことになります。

あー、なのでイベント用メソッドはProtectedで問題なし、というのは間違いですね。Protected Friendじゃないといけないっす。

ちなみにインスタンスを生成した際、親DataGridViewに追加するのではなく、親.TopLevelControl.Controls.Addとしたほうがいいと思われ。そのほうが余分な制御が走らない分いいと思いますわ。もちろん追加するインスタンスの座標は-1でも掛けて目に見えない場所にしておくこと。そうすればOK。

それでPaintメソッドはもう少し話が続くので次回の記事までひっぱります。
毎回こんな処理やられたら大変だー、というところです。

複数バージョンのOfficeを同居すると・・・

複数のバージョンの Office がインストールされている場合の Office オートメーションについてKBで公開された記事なんだけど、これを読むと「なんちゅー作りだ」と思ってしまう・・・。

原則異なるバージョンの同居を許さない、という思想だからなんだろうけどねぇ・・・。インストール時、バージョンによっては起動時にPROGIDの値を更新してるってんなら、そりゃ毎回挙動が代わるわな。