Excel VBA クリップボードに値を設定

 https://qiita.com/Q11Q/items/c688646dfdb5923c0ebd


今使っているアプリに組み込んでテストをするために転記。

ExcelのSelection.Copyだと改行が含まれてしまうので改行が含まれないようにするためにクリップボードへ直接値を設定してみる。

やっぱりすごい人がいっぱいいるんだなぁ。



標準モジュール

' https://msdn.microsoft.com/ja-jp/vba/access-vba/articles/retrieve-information-from-the-clipboard

' http://keirivba.hateblo.jp/entry/2017/11/03/234832

' [Docs/Windows/Desktop/Data Exchange/Clipboard/Clipboard Overviews](https://docs.microsoft.com/en-us/windows/desktop/dataxchg/clipboard-overviews)

'''''''''''''''''''''''''''''''''''''

' Class Module

' ClipBoardClass

' Win 64/32

''''''''''''''''''''''''''''''''''''''''

#If VBA7 Then

Private Declare PtrSafe Function OpenClipboard Lib "User32" (ByVal hWnd As LongPtr) As Long

Private Declare PtrSafe Function CloseClipboard Lib "User32" () As Long

Private Declare PtrSafe Function GetClipboardData Lib "User32" (ByVal wFormat As _

LongPtr) As Long

Private Declare PtrSafe Function EmptyClipboard Lib "User32" () As LongPtr

Private Declare PtrSafe Function GlobalSize Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr

'クリップボードにデータを渡す

Private Declare PtrSafe Function SetClipboardData Lib "User32" ( _

ByVal wFormat As Long, _

ByVal hMem As LongPtr) As LongPtr


'指定したサイズ分のメモリを割り当て

Private Declare PtrSafe Function GlobalAlloc Lib "kernel32" ( _

ByVal wFlags As Long, _

ByVal dwBytes As LongPtr) As LongPtr


'メモリブロックをロックして最初の1バイトへのポインタを返す

Private Declare PtrSafe Function GlobalLock Lib "kernel32" ( _

ByVal hMem As LongPtr) As LongPtr


'メモリのロックを解除

Private Declare PtrSafe Function GlobalUnlock Lib "kernel32" ( _

ByVal hMem As LongPtr) As Long

'バッファに文字列をコピー

Private Declare PtrSafe Function lstrcpy Lib "kernel32" ( _

ByVal lpString1 As Any, _

ByVal lpString2 As Any) As LongPtr

#Else

'Open close get globalalloc GlobalLock GloablUnlock GlobalSize lstcpy Empty

Private Declare Function OpenClipboard Lib "User32" (ByVal hWnd As Long) _

As Long

Private Declare Function CloseClipboard Lib "User32" () As Long

Private Declare Function GetClipboardData Lib "User32" (ByVal wFormat As _

Long) As Long

'クリップボードにデータを渡す

Private Declare Function SetClipboardData Lib "User32" ( _

ByVal wFormat As Long, _

ByVal hMem As Long) As Long

Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags, ByVal _

dwBytes As Long) As Long

Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) _

As Long

Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) _

As Long

Private Declare Function GlobalSize Lib "kernel32" (ByVal hMem As Long) _

As Long

Private Declare Function lstrcpy Lib "kernel32" (ByVal lpString1 As Any, _

ByVal lpString2 As Any) As Long

#End If

'GlobalALock

Private Const GHND = &H42

' SetClipboadData

Private Const CF_TEXT = &H1

Private Const CF_LINK = &HBF00

Private Const CF_BITMAP = 2

Private Const CF_METAFILE = 3

Private Const CF_DIB = 8

Private Const CF_PALETTE = 9

Private Const MAXSIZE = 4096

Public Sub ClsClipBoardClass_Err()

OpenClipboard (0)

EmptyClipboard

CloseClipboard

End Sub


Public Function SetClipBoard(MyString As String)

#If VBA7 Then

Dim hGlobalMemory As LongPtr

Dim lpGlobalMemory As LongPtr

Dim hClipMemory As LongPtr

Dim X As LongPtr

#Else

Dim hGlobalMemory As Long

Dim lpGlobalMemory As Long

Dim hClipMemory As Long

Dim X As Long

#End If

'移動可能なグローバルメモリを割り当て

hGlobalMemory = GlobalAlloc(GHND, LenB(MyString) + 1)

'ブロックをロックして、メモリへのfarポインタを取得

lpGlobalMemory = GlobalLock(hGlobalMemory)

'文字列をグローバルメモリへコピー

lpGlobalMemory = lstrcpy(lpGlobalMemory, MyString)

'メモリのロックを解除します。

If GlobalUnlock(hGlobalMemory) <> 0 Then

MsgBox "メモリのロックを解除できません" & vbCrLf & _

"処理が失敗しました"

GoTo OutOfHere2

End If


'データをコピーするクリップボードを開く

If OpenClipboard(0&) = 0 Then

MsgBox "クリップボードを開くことができません" & vbCrLf & _

"処理が失敗しました"

Exit Function

End If


' クリップボードの内容を消去

X = EmptyClipboard()


' データをクリップボードへコピー

hClipMemory = SetClipboardData(CF_TEXT, hGlobalMemory)


OutOfHere2:

'クリップボードの状態チェック

If CloseClipboard() = 0 Then

MsgBox "クリップボードを閉じることができません"

End If

End Function


Public Function ClipBoard_GetData()

#If VBA7 Then

Dim hClipMemory As LongPtr

Dim lpClipMemory As LongPtr

#Else

Dim hClipMemory As Long

Dim lpClipMemory As Long

#End If

Dim MyString As String

Dim RetVal As Long


If OpenClipboard(0&) = 0 Then

MsgBox "Cannot open Clipboard. Another app. may have it open"

Exit Function

End If


' Obtain the handle to the global memory

' block that is referencing the text.

hClipMemory = GetClipboardData(CF_TEXT)

If IsNull(hClipMemory) Then

MsgBox "Could not allocate memory"

GoTo OutOfHere

End If


' Lock Clipboard memory so we can reference

' the actual data string.

lpClipMemory = GlobalLock(hClipMemory)


If Not IsNull(lpClipMemory) Then

MyString = Space$(MAXSIZE)

RetVal = lstrcpy(MyString, lpClipMemory)

RetVal = GlobalUnlock(hClipMemory)

' Peel off the null terminating character.

MyString = Mid(MyString, 1, InStr(1, MyString, Chr$(0), 0) - 1)

Else

MsgBox "Could not lock memory to copy string from."

End If


OutOfHere:

RetVal = CloseClipboard()

ClipBoard_GetData = MyString

End Function


''''SampleCode For Module

' 標準モジュール用のコードです

' Classモジュールでは動きません。かならず標準モジュールに記述してください。


'Sub ClipBoardClassTest()

'' 標準モジュール用のコードです

'On Error GoTo Clip_Error

'' Dim and New For ClipBoadClass

'Dim clsClip As ClipBoardClass: Set clsClip = New ClipBoardClass

'clsClip.SetClipBoard ("test1")

'Debug.Print clsClip.ClipBoard_GetData

'Clip_Error:

'Call clsClip.ClsClipBoardClass_Err

'End Sub



利用例

Sub ClipBoardClassTest()

' 標準モジュール用のコードです

On Error GoTo Clip_Error

' Dim and New For ClipBoadClass

Dim clsClip As ClipBoardClass: Set clsClip = New ClipBoardClass

clsClip.SetClipBoard ("test1")

Debug.Print clsClip.ClipBoard_GetData

Clip_Error:

Call clsClip.ClsClipBoardClass_Err

End Sub

Excel VBA 画面のハードコピーを取得(1)

 

VBAで指定した画面を保存する方法を探していたらいいのがあった。

PrintScreenで取得してクリップボードからという手もいいんだけど、別のソフトが邪魔をするケースがあったので代案を探していた。

素晴らしい。同じものを探すのが大変なのでなくなる前に転記。

https://blog.systemjp.net/entry/2014/04/15/142736

'******************************************************************************

'* 【参考元】

'*   Microsoft Support

'*   画面、フォーム、ウィンドウを取り込んで印刷する方法

'*   文書番号: 161299

'*   http://support.microsoft.com/kb/161299/ja

'******************************************************************************

Option Explicit

Option Base 0

Private Type PALETTEENTRY

    peRed As Byte

    peGreen As Byte

    peBlue As Byte

    peFlags As Byte

End Type

Private Type LOGPALETTE

    palVersion As Integer

    palNumEntries As Integer

    palPalEntry(255) As PALETTEENTRY  ' Enough for 256 colors.

End Type

Private Type GUID

    Data1 As Long

    Data2 As Integer

    Data3 As Integer

    Data4(7) As Byte

End Type

Private Type RECT

    Left As Long

    Top As Long

    Right As Long

    Bottom As Long

End Type

Private Type PicBmp

   Size As Long

   Type As Long

   hBmp As Long

   hPal As Long

   Reserved As Long

End Type

Private Const RASTERCAPS As Long = 38

Private Const RC_PALETTE As Long = &H100

Private Const SIZEPALETTE As Long = 104

Private Declare Function CreateCompatibleDC Lib "GDI32" (ByVal hDC As Long) As Long

Private Declare Function CreateCompatibleBitmap Lib "GDI32" (ByVal hDC As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long

Private Declare Function GetDeviceCaps Lib "GDI32" (ByVal hDC As Long, ByVal iCapabilitiy As Long) As Long

Private Declare Function GetSystemPaletteEntries Lib "GDI32" (ByVal hDC As Long, ByVal wStartIndex As Long, ByVal wNumEntries As Long, lpPaletteEntries As PALETTEENTRY) As Long

Private Declare Function CreatePalette Lib "GDI32" (lpLogPalette As LOGPALETTE) As Long

Private Declare Function SelectObject Lib "GDI32" (ByVal hDC As Long, ByVal hObject As Long) As Long

Private Declare Function BitBlt Lib "GDI32" (ByVal hDCDest As Long, ByVal XDest As Long, ByVal YDest As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hDCSrc As Long, ByVal XSrc As Long, ByVal YSrc As Long, ByVal dwRop As Long) As Long

Private Declare Function DeleteDC Lib "GDI32" (ByVal hDC As Long) As Long

Private Declare Function GetForegroundWindow Lib "user32" () As Long

Private Declare Function SelectPalette Lib "GDI32" (ByVal hDC As Long, ByVal hPalette As Long, ByVal bForceBackground As Long) As Long

Private Declare Function RealizePalette Lib "GDI32" (ByVal hDC As Long) As Long

Private Declare Function GetWindowDC Lib "user32" (ByVal hWnd As Long) As Long

Private Declare Function GetDC Lib "user32" (ByVal hWnd As Long) As Long

Private Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, lpRect As RECT) As Long

Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Long, ByVal hDC As Long) As Long

Private Declare Function GetDesktopWindow Lib "user32" () As Long

Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (PicDesc As PicBmp, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long

Private Const vbPicTypeBitmap As Long = 1

Private Const vbSrcCopy As Long = &HCC0020

Private Const SM_CXSCREEN As Long = 0

Private Const SM_CYSCREEN As Long = 1

Private Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' CreateBitmapPicture

'    - Creates a bitmap type Picture object from a bitmap and

'      palette.

'

' hBmp

'    - Handle to a bitmap.

'

' hPal

'    - Handle to a Palette.

'    - Can be null if the bitmap doesn't use a palette.

'

' Returns

'    - Returns a Picture object containing the bitmap.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Public Function CreateBitmapPicture(ByVal hBmp As Long, ByVal hPal As Long) As IPictureDisp

    ' Fill in with IDispatch Interface ID.

    Dim IID_IDispatch As GUID

    With IID_IDispatch

        .Data1 = &H20400

        .Data4(0) = &HC0

        .Data4(7) = &H46

    End With

   

    ' Fill Pic with necessary parts.

    Dim Pic As PicBmp

    With Pic

        .Size = Len(Pic)          ' Length of structure.

        .Type = vbPicTypeBitmap   ' Type of Picture (bitmap).

        .hBmp = hBmp              ' Handle to bitmap.

        .hPal = hPal              ' Handle to palette (may be null).

    End With

   

    ' Create Picture object.

    Dim IPic As IPicture

    Dim r As Long

    r = OleCreatePictureIndirect(Pic, IID_IDispatch, 1, IPic)

   

    ' Return the new Picture object.

    Set CreateBitmapPicture = IPic

   

End Function

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' CaptureWindow

'    - Captures any portion of a window.

'

' hWndSrc

'    - Handle to the window to be captured.

'

' Client

'    - If True CaptureWindow captures from the client area of the

'      window.

'    - If False CaptureWindow captures from the entire window.

'

' LeftSrc, TopSrc, WidthSrc, HeightSrc

'    - Specify the portion of the window to capture.

'    - Dimensions need to be specified in pixels.

'

' Returns

'    - Returns a Picture object containing a bitmap of the specified

'      portion of the window that was captured.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Public Function CaptureWindow(ByVal hWndSrc As Long, ByVal Client As Boolean, ByVal LeftSrc As Long, ByVal TopSrc As Long, ByVal WidthSrc As Long, ByVal HeightSrc As Long) As IPictureDisp

   

    Dim r As Long

   

    ' Depending on the value of Client get the proper device context.

    Dim hDCSrc As Long

    hDCSrc = IIf(Client, GetDC(hWndSrc), GetWindowDC(hWndSrc))

   

    ' Create a memory device context for the copy process.

    Dim hDCMemory As Long

    hDCMemory = CreateCompatibleDC(hDCSrc)

   

    ' Create a bitmap and place it in the memory DC.

    Dim hBmp As Long, hBmpPrev As Long

    hBmp = CreateCompatibleBitmap(hDCSrc, WidthSrc, HeightSrc)

    hBmpPrev = SelectObject(hDCMemory, hBmp)

   

    ' Get screen properties.

    Dim RasterCapsScrn As Long, HasPaletteScrn As Long, PaletteSizeScrn As Long

    RasterCapsScrn = GetDeviceCaps(hDCSrc, RASTERCAPS)   ' Raster capabilities.

    HasPaletteScrn = RasterCapsScrn And RC_PALETTE       ' Palette support.

    PaletteSizeScrn = GetDeviceCaps(hDCSrc, SIZEPALETTE) ' Size of palette.

   

    ' If the screen has a palette make a copy and realize it.

    Dim hPal As Long, hPalPrev As Long, LogPal As LOGPALETTE

    If HasPaletteScrn And (PaletteSizeScrn = 256) Then

        ' Create a copy of the system palette.

        LogPal.palVersion = &H300

        LogPal.palNumEntries = 256

        r = GetSystemPaletteEntries(hDCSrc, 0, 256, LogPal.palPalEntry(0))

        hPal = CreatePalette(LogPal)

        ' Select the new palette into the memory DC and realize it.

        hPalPrev = SelectPalette(hDCMemory, hPal, 0)

        r = RealizePalette(hDCMemory)

    End If

   

    ' Copy the on-screen image into the memory DC.

    r = BitBlt(hDCMemory, 0, 0, WidthSrc, HeightSrc, hDCSrc, LeftSrc, TopSrc, vbSrcCopy)

   

    ' Remove the new copy of the  on-screen image.

    hBmp = SelectObject(hDCMemory, hBmpPrev)

   

    ' If the screen has a palette get back the palette that was

    ' selected in previously.

    If HasPaletteScrn And (PaletteSizeScrn = 256) Then

        hPal = SelectPalette(hDCMemory, hPalPrev, 0)

    End If

   

    ' Release the device context resources back to the system.

    r = DeleteDC(hDCMemory)

    r = ReleaseDC(hWndSrc, hDCSrc)

   

    ' Call CreateBitmapPicture to create a picture object from the

    ' bitmap and palette handles. Then return the resulting picture

    ' object.

    Set CaptureWindow = CreateBitmapPicture(hBmp, hPal)

End Function

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' CaptureScreen

'    - Captures the entire screen.

'

' Returns

'    - Returns a Picture object containing a bitmap of the screen.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Public Function CaptureScreen() As IPictureDisp

    ' Get a handle to the desktop window.

    Dim hWndScreen As Long

    hWndScreen = GetDesktopWindow()

   

    Dim cxScreen As Long, cyScreen As Long

    cxScreen = GetSystemMetrics(SM_CXSCREEN)

    cyScreen = GetSystemMetrics(SM_CYSCREEN)

   

    ' Call CaptureWindow to capture the entire desktop give the handle

    ' and return the resulting Picture object.

    Set CaptureScreen = CaptureWindow(hWndScreen, False, 0, 0, cxScreen, cyScreen)

   

End Function

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

'

' CaptureActiveWindow

'    - Captures the currently active window on the screen.

'

' Returns

'    - Returns a Picture object containing a bitmap of the active

'      window.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Public Function CaptureActiveWindow() As IPictureDisp

    Dim r As Long

    ' Get a handle to the active/foreground window.

    Dim hWndActive As Long

    hWndActive = GetForegroundWindow()

    ' Get the dimensions of the window.

    Dim RectActive As RECT

    r = GetWindowRect(hWndActive, RectActive)

    ' Call CaptureWindow to capture the active window given its

    ' handle and return the Resulting Picture object.

    Set CaptureActiveWindow = CaptureWindow(hWndActive, False, 0, 0, RectActive.Right - RectActive.Left, RectActive.Bottom - RectActive.Top)

End Function

使用例1 : 画面全体の画像をファイルに保存する

Public Sub SavePictureOfScreen()

    Dim pic As IPictureDisp

    Set pic = CaptureScreen()

    Call SavePicture(pic, "C:\screen.bmp")

End Sub

使用例2 : アクティブウィンドウの画像をファイルに保存する

※保存した画像の一部がおかしい不具合あり。原因は調査中。

Public Sub SavePictureOfActiveWindow()

    Dim pic As IPictureDisp

    Set pic = CaptureActiveWindow()

    Call SavePicture(pic, "C:\activewindow.bmp")

End Sub



追記:

 SavePictureOfActiveWindowでIEの画像を取ると画像が白くなってしまう不具合がある。ほかのサイトでも同じようなソースだったので何がおかしいのかよくわからない。ウィンドウハンドルの取り方?Sleepが必要?VB6のソースでは問題なさそうだった・・・。仕方ないので画像の加工で対応をしようかと思い、サンプルソースを探してきた。


 https://plaza.rakuten.co.jp/tobiinsky/diary/200909160001/

'*****************************************************************************

'* トリミング処理

'*

'*****************************************************************************

Private Function fncPicTrimming(strPass As String, sglT As Single, sglB As Single, sglL As Single, sglR As Single) As String


Dim strWrkPass As String


fncPicTrimming = ""


Application.ScreenUpdating = False


'トリミング前保存場所

strWrkPass = ThisWorkbook.Path + "\image1.bmp"


'取得画像をそのまま処理すると画像がボケることがあるので、一度フォームに貼付→再保存する

UserForm1.Picture = LoadPicture(strPass)

SavePicture UserForm1.Picture, strWrkPass

UserForm1.Picture = LoadPicture()


'画像の挿入

ActiveSheet.Pictures.Insert(strWrkPass).Select


'トリミング実行 トリミングは実際に「マクロの記録」を利用して、その値をそのままコードへ(小数点をカットしないこと!)

Selection.ShapeRange.PictureFormat.CropTop = sglT

Selection.ShapeRange.PictureFormat.CropBottom = sglB

Selection.ShapeRange.PictureFormat.CropLeft = sglL

Selection.ShapeRange.PictureFormat.CropRight = sglR


'トリミング後保存場所

strWrkPass = ThisWorkbook.Path + "\image2.bmp"


'トリミング後の画像をクリップボードへコピーし、それを保存(excelではこれしか方法がなさそう)

Selection.CopyPicture Appearance:=xlScreen, Format:=xlBitmap

SavePicture GetBitMap(), strWrkPass


Selection.Delete


Application.ScreenUpdating = True


fncPicTrimming = strWrkPass


End Function

Excel VBA Accessのデータを取得

ExcelのVBAからAccessへ接続しデータを取得するコードのサンプル。接続方法はADOを利用する。接続する側にはAccessを入れる必要はないので管理者の端末にだけAccessがインストールされていればよい。

ExcelのVBAの参照設定の追加でVBE(VBAの編集画面)で、ツール→参照

ここで、
Microsoft ActiveX Data Objects 2.X Library
または
Microsoft ActiveX Data Objects 6.1 Library
を選択しておく。

標準モジュールにコードをGetData関数、GetHaiban関数、ExecuteUpdInsDelSQL関数を作成しておけば、どこでも利用できるようになる。一か所でまとめておけば何かあった時にも対応しやすい。

コードの例)

Private Const DB_FILEPATH = "testdb.accdb"
'データ取得サンプル
Public Sub getdatasanm()
    Dim str As String
    Dim rs As ADODB.Recordset
    str = " SELECT * FROM T_サンプル"
    If GetData(str, rs) Then
        Do Until rs.EOF
            MsgBox (rs!名前)
            rs.MoveNext
        Loop
        
        MsgBox (GetHaiban)
    
        str = " UPDATE T_サンプル SET 住所 = '日本' "
        ExecuteUpdInsDelSQL (str)
    End If
    
End Sub
'----------------------------------------------------------------------------------------
'切断型データベースによるデータ取得
' 引数1:実行するSQL
' 引数2:検索結果を格納するRecordset(参照渡し)
'----------------------------------------------------------------------------------------
Public Function GetData(ByVal strSQL As String, ByRef rdSet As ADODB.Recordset) As Boolean
    Dim myCon As New ADODB.Connection
    Dim myRecordSet As New ADODB.Recordset
    Dim strConnectionString As String
    Dim rdReturn As ADODB.Recordset
On Error GoTo CatchError
    strConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_FILEPATH
    myCon.ConnectionString = strConnectionString
    myCon.Open
    myRecordSet.CursorLocation = adUseClient
    '参照のみなので、読み取り専用(adLockReadOnly)で開く
    '更新の場合は、レコード単位排他ロック(adLockPessimistic)を指定
    myRecordSet.Open strSQL, myCon, adOpenDynamic, adLockReadOnly
    
    'データの取得が終わったらすぐに接続を切る。この時点でmyRecordSetはAccessとは切り離されるので、自由に利用できる
    '.NETのDataTableのイメージでよい
    Set myRecordSet.ActiveConnection = Nothing
    myCon.Close
    Set myCon = Nothing
    If myRecordSet.EOF Then
        GetData = False
        Exit Function
    Else
        GetData = True
        '列数のカウント
        Dim i As Long
        Set rdReturn = New ADODB.Recordset
        '戻り値のRecordSetを定義
        '  切断型レコードセットを利用しているのでそのままSetすれば呼び出し元でも利用できると思われるが、
        '  念のために別のレコードセットを定義。データ量によっては速度が遅く感じるかもしれないのでその際には修正検討。
        For i = 0 To myRecordSet.Fields.Count - 1
            rdReturn.Fields.Append myRecordSet.Fields(i).Name, myRecordSet.Fields(i).Type, myRecordSet.Fields(i).DefinedSize, adFldIsNullable
        Next
        rdReturn.Open
        Do Until myRecordSet.EOF
            With rdReturn
                .AddNew
                For i = 0 To myRecordSet.Fields.Count - 1
                    .Fields(i) = myRecordSet.Fields(i)
                Next
                .Update
            End With
            myRecordSet.MoveNext
        Loop
    End If
    rdReturn.MoveFirst
    Set rdSet = rdReturn
    '参照渡しなので下記のようにCloseしてしまうと呼び出し元で利用できない
    'rdReturn.Close
    myRecordSet.Close
    Set myRecordSet = Nothing
    Exit Function
CatchError:
    MsgBox "エラーが発生" & vbCrLf & Err.Number & vbCrLf & Err.Description, vbExclamation
End Function
'----------------------------------------------------------------------------------------
'配番テーブルを利用
'----------------------------------------------------------------------------------------
Public Function GetHaiban() As Long
    Dim myCon As New ADODB.Connection
    Dim myRecordSet As New ADODB.Recordset
    Dim strConnectionString As String
    Dim strSQL As String
    On Error GoTo CatchError
    strSQL = " SELECT * FROM T_配番 WHERE ID = 1 "
    strConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_FILEPATH
    myCon.ConnectionString = strConnectionString
    myCon.Open
    myCon.BeginTrans
    myRecordSet.Open strSQL, myCon, adOpenDynamic
    GetHaiban = myRecordSet!配番No
    Dim strUpdSQL As String
    strUpdSQL = " UPDATE T_配番 SET 配番No = " & myRecordSet!配番No + 1 & " WHERE ID = 1 "
    myCon.Execute strUpdSQL
    myCon.CommitTrans
    myRecordSet.Close
    Set myRecordSet = Nothing
    myCon.Close
    Set myCon = Nothing
    Exit Function
CatchError:
    MsgBox "エラーが発生" & vbCrLf & Err.Number & vbCrLf & Err.Description, vbExclamation
End Function
'----------------------------------------------------------------------------------------
'データ更新SQLを実行
' 引数1:実行するSQL(複数のSQLを同一トランザクションで実行したい場合は「;」で区切る
' 引数2:(オプション)複数のSQLを同一トランザクションで実行する場合は、Trueを指定
'----------------------------------------------------------------------------------------
Public Sub ExecuteUpdInsDelSQL(ByVal strSQL As String, Optional ByVal blnIsSomeSQL As Boolean = False)
    Dim myCon As New ADODB.Connection
    Dim myRecordSet As New ADODB.Recordset
    Dim strConnectionString As String
    On Error GoTo CatchError
    strConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_FILEPATH
    myCon.ConnectionString = strConnectionString
    myCon.Open
    myCon.BeginTrans
    If blnIsSomeSQL Then
        Dim sql() As String
        sql = Split(strSQL, ";")
        For i = 0 To UBound(sql)
            If Trim(sql(i)) <> "" Then
                myRecordSet.Open sql(i), myCon, adOpenDynamic, adLockPessimistic
            End If
        Next
    Else
        myRecordSet.Open strSQL, myCon, adOpenDynamic, adLockPessimistic
    End If
    myCon.CommitTrans
    Set myRecordSet = Nothing
    myCon.Close
    Set myCon = Nothing
    Exit Sub
CatchError:
    MsgBox "エラーが発生" & vbCrLf & Err.Number & vbCrLf & Err.Description, vbExclamation
End Sub
Public Function NullSpace(ByVal obj) As Variant
    If IsNull(obj) Then
        NullSpace = ""
    Else
        NullSpace = obj
    End If
End Function
Public Function NullZero(ByVal obj) As Variant
    If IsNull(obj) Then
        NullZero = 0
    Else
        NullZero = obj
    End If
End Function


Excel VBA 速度改善

仕事でExcelVBAを触る機会があったので、その時の改善内容をメモ。
キー項目が複数個あるデータをループでチェックする際に処理速度が非常に遅いマクロがあった。

チェック対象
キー項目1 キー項目2 チェック結果
0001 A001 100,000
0002 A003 10,000

チェックリスト
キー項目1 キー項目2 値範囲開始 値範囲終了
0001 A001 10,000 500,000
0002 A003 100,000 500,000

チェック対象を1行読んで、その値でチェックリストを全件ループし、キー項目1とキー項目2が一致していればその値の範囲をチェックするような仕組みだった。
例)
 For i = 1 To  チェック対象MAX行
      For j = 1 To チェックリストMAX行
   If キー項目1(チェック対象) = キー項目1(チェックリスト) And キー項目2(チェック対象) = キー項目2(チェックリスト) Then

                'チェック処理
 
         End If
    Next j
 Next i


チェック対象とチェックリストが少なければ大した問題にはならなかったが、データ件数が増えてくると1回の処理が30分かかったりするようになってしまった。まぁ当然だけども・・・。
とはいえ、ある程度稼働した後だったのであんまり手を入れるのが怖かったので下記の関数を作った。っていうか探してきた。

'---------------------------------------------------------
' 文字列・数字セルを検索
' シート名と列位置を指定し検索する。該当した行位置をカンマ区切りで返す。
'  1件ずつループしながらIf関数で1行ずつ判定するより圧倒的に高速
'  複数条件は対応外なので、この関数で該当行を絞ったうえで、If関数で詳細なチェックをかけたほうが高速になる
' 利用例)GetSearchStringNumberCell("0001","A:A","データ")
' 戻り値例)4,15,20
'---------------------------------------------------------
Public Function GetSearchStringNumberCell(ByVal strValue As String, ByVal strColumnRange As String, ByVal strSheetName As String) As String

    Dim rng As Range
    Dim adr As String
    Dim strResult As String

    GetSearchStringNumberCell = ""
  
    Set rng = Sheets(strSheetName).Columns(strColumnRange).Find(strValue)
  
    If rng Is Nothing Then
        Exit Function
    Else
        adr = rng.Address
        strResult = rng.Row
    End If

    Do
    Set rng = Sheets(strSheetName).Columns(strColumnRange).FindNext(After:=rng)
    If rng.Address = adr Then
            Exit Do
        Else
            strResult = strResult & "," & rng.Row
        End If
    Loop

    GetSearchStringNumberCell = strResult

End Function

Excelの標準機能を使うと処理速度が速くなるのは有名な話なので、検索する処理をExcelのFind関数に任せてみた。
Find関数は1項目しか検索できないので、1回で処理しようとすると複数項目を検索しようとすると検索用のキー項目を作る必要がある。
例)キー項目1+キー項目2の値を横の列に作成して検索。(0001A002)

でも、すでに稼働しているところに手を入れるのは怖いので、この関数をここに入れてみた。

例)

 For i = 1 To  チェック対象MAX行
      Dim strValue As String
      Dim strArr() As String
  strValue = GetSearchStringNumberCell(キー項目1(チェック対象),"A:A","チェック対象
")
      strArr = Split(strValue,",")

      For jj = 0 To Ubound(strArr)
         j = strArr(jj)

   If キー項目1(チェック対象) = キー項目1(チェックリスト) And キー項目2(チェック対象) = キー項目2(チェックリスト) Then

                'チェック処理
 
         End If
    Next j
 Next i

GetSearchStringNumberCell関数は検索値、検索範囲(列など)、検索シートを指定し、実行することで検索値が含まれているセルの行位置をカンマ区切りで返してくれる。
そのため、戻り値をカンマでSplitすることで、チェックする必要のある行位置のみをループすればよいことになる。

この処理のおかげで処理速度が30分が30秒以下で終わるようになった。やっぱりExcel関数をなるべく使うほうがいいんだけど、使い方に慣れた関数でないとループやIf関数で処理をしたくなる。

あとは2次元配列にしたりすれば貼り付けの速度もかなり早くなるんだろうけど、そこまで改修しなくても問題ない速度になったのでここまでにしておこうと思う。
業務上支障のない速度になっていれば問題ないし、変に手を入れて、30秒が10秒になる程度であればコーヒーでも飲んでてもらえればいいし。
※改修とテストにそれ以上時間かかっちゃうので優先順位が低いってのは内緒

ちなみに日付セルを検索する場合はこっち
'---------------------------------------------------------
' 日付セルを検索
' シート名と列位置を指定し検索する。該当した行位置をカンマ区切りで返す。
'  1件ずつループしながらIf関数で1行ずつ判定するより圧倒的に高速
'  複数条件は対応外なので、この関数で該当行を絞ったうえで、If関数で詳細なチェックをかけたほうが高速になる
' 利用例)GetSearchDateCell("2020/08/01","A:A","データ")
' 戻り値例)4,15,20
'---------------------------------------------------------

Public Function GetSearchDateCell(ByVal strValue As Date, ByVal strColumnRange As String, ByVal strSheetName As String) As String

    Dim rng As Range
    Dim adr As String
    Dim strResult As String

    GetSearchDateCell = ""
  
  Set rng = Sheets(strSheetName).Columns(strColumnRange).Find(strValue)
  
  If rng Is Nothing Then
        Exit Function
    Else
        adr = rng.Address
        strResult = rng.Row
    End If

    Do
    Set rng = Sheets(strSheetName).Columns(strColumnRange).FindNext(After:=rng)
    If rng.Address = adr Then
            Exit Do
        Else
            strResult = strResult & "," & rng.Row
        End If
    Loop

    GetSearchDateCell = strResult

End Function

引数が変わっただけ・・・。Object型とかVariant型にすれば統一できそうな気もする。

WPFのスタイルの継承について

WPFでアプリを作る際にスタイルの継承をしてみたかったので調べてみた。

「BasedOn」を指定することでスタイルの継承を行うことができる。
そのため、「基本スタイルの定義」->(継承)「コントロール共通のスタイルの定義」->(継承)「プロジェクトごとのスタイルの定義」とすることで、業務用アプリなどの同じ意味を持つコントロールの定義を一括で定義することができる。
業務用アプリだと「得意先CD」「得意先名」など同じ意味を持つテキストボックスコントロールが各画面にばらまかれるため、MaxLengthや色などを変更するときに作り方次第では画面ごとに修正が必要だが、共通スタイルを定義しておくことでResourceを修正するだけで全画面が直る。

ResourceDictionary.MergedDictionariesで継承元の定義しておいたXAMLを読み込んでいる。
最後にApp.xamlでも同じように定義を読み込む

サンプル)
[BasicResource.xaml]
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:SystemTemplate.resource"
                    >
    
    <!-- Active状態の背景色 -->
    <SolidColorBrush x:Key="ActiveBackColor" Color="#FFF4FDBE"></SolidColorBrush>

    <!-- テーマカラー -->
    <SolidColorBrush x:Key="WindowColor" Color="#FFF4F9FF"></SolidColorBrush>

    <!--#region 継承元のスタイル-->
    <!-- 継承元のフォームベースのスタイル -->
    <Style x:Key="WindowStyleBase" TargetType="Window">
        <!--<Setter Property="Icon" Value="images/icon.ico"></Setter>-->
        <Setter Property="FontFamily" Value="MS Gothic" />
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="Background" Value="{StaticResource WindowColor}"></Setter>
        <Setter Property="WindowStyle" Value="SingleBorderWindow"></Setter>
        <Setter Property="TextOptions.TextFormattingMode" Value="Ideal"></Setter>
        <Setter Property="TextOptions.TextRenderingMode" Value="Auto"></Setter>
    </Style>


    <Style TargetType="UserControl" x:Key="UserControlBaseStyle">
        <Setter Property="FontSize" Value="11.5"/>
        <Setter Property="FontFamily" Value="MS Gothic"/>
    </Style>

    <Style x:Key="TextBlockStyleBase" TargetType="TextBlock">
        <Setter Property="FontFamily" Value="MS Gothic" />
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="TextAlignment" Value="Left"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
    </Style>

    <Style x:Key="TextBoxStyleBase" TargetType="TextBox">
        <Setter Property="FontFamily" Value="MS Gothic" />
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="TextAlignment" Value="Left"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Height" Value="25"/>

        <!-- フォーカス時に背景色を設定 -->
        <Style.Triggers>
            <Trigger Property="IsFocused" Value="True">
                <!--IsFocusedがTrueの場合、下の値を適用-->
                <Setter Property="Background" Value="{StaticResource ActiveBackColor}" />
            </Trigger>
            <Trigger Property="IsFocused" Value="False">
                <Setter Property="Background" Value="White" />
            </Trigger>
        </Style.Triggers>

    </Style>


    <Style x:Key="PasswordBoxStyleBase" TargetType="PasswordBox">
        <Setter Property="FontFamily" Value="MS Gothic" />
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Height" Value="25"/>

        <!-- フォーカス時に背景色を設定 -->
        <Style.Triggers>
            <Trigger Property="IsFocused" Value="True">
                <!--IsFocusedがTrueの場合、下の値を適用-->
                <Setter Property="Background" Value="{StaticResource ActiveBackColor}" />
            </Trigger>
            <Trigger Property="IsFocused" Value="False">
                <Setter Property="Background" Value="White" />
            </Trigger>
        </Style.Triggers>

    </Style>

    <Style x:Key="LabelStyleBaseNoHeight" TargetType="Label">
        <Setter Property="FontFamily" Value="MS Gothic" />
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="BorderBrush" Value="Black"/>
        <Setter Property="BorderThickness" Value="1"/>
    </Style>

    <Style x:Key="LabelStyleBase" TargetType="Label" BasedOn="{StaticResource LabelStyleBaseNoHeight}">
        <Setter Property="Height" Value="25"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
    </Style>

    <Style x:Key="DataGridStyleBase" TargetType="DataGrid">
        <Setter Property="FontFamily" Value="MS Gothic" />
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="HorizontalGridLinesBrush" Value="Silver"/>
        <Setter Property="VerticalGridLinesBrush" Value="Silver"/>
        <Setter Property="AlternatingRowBackground" Value="#FFD8FFFD"/>
        <!--<Setter Property="Height" Value="25"/>-->
    </Style>

    <!-- 影付きのボタンにするためのエフェクト -->
    <DropShadowEffect x:Key="ButtonEffect" BlurRadius="0" RenderingBias="Quality" ShadowDepth="2"/>
    <Style x:Key="ButtonStyleBase" TargetType="Button">
        <Setter Property="FontFamily" Value="MS Gothic" />
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="BorderBrush" Value="Black"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Height" Value="25"/>
        <Setter Property="Effect" Value="{StaticResource ButtonEffect}"/>

        <Style.Triggers>

            <!-- フォーカス時の背景色 -->
            <Trigger Property="IsFocused" Value="True">
                <Setter Property="Background" Value="{StaticResource ActiveBackColor}"></Setter>
            </Trigger>
        </Style.Triggers>

    </Style>

    <!-- TextBlock縦書 -->
    <Style x:Key="TextBlockVerticalWriting"  TargetType="{x:Type TextBlock}">
        <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
        <Setter Property="Margin" Value="0.0,0.0"/>
        <Setter Property="Width" Value="Auto"/>
        <Setter Property="TextOptions.TextFormattingMode" Value="Display"/>
        <Setter Property="RenderTransform">
            <Setter.Value>
                <TransformGroup>
                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                    <SkewTransform AngleX="0" AngleY="0"/>
                    <RotateTransform Angle="0"/>
                    <TranslateTransform X="0" Y="0"/>
                </TransformGroup>
            </Setter.Value>
        </Setter>
        <Setter Property="LayoutTransform">
            <Setter.Value>
                <TransformGroup>
                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                    <SkewTransform AngleX="0" AngleY="0"/>
                    <RotateTransform Angle="90"/>
                    <TranslateTransform X="0" Y="0"/>
                </TransformGroup>
            </Setter.Value>
        </Setter>
    </Style>

    <!--#endregion-->
</ResourceDictionary>

[ControlResource] コントロールごとの共通スタイルを定義
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:SystemTemplate.resource"
                    >

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="BaseResorce.xaml"/>
    </ResourceDictionary.MergedDictionaries>

    <!--#region 継承後のスタイル-->

    <Style x:Key="NormalLabel" TargetType="Label" BasedOn="{StaticResource LabelStyleBase}">
        <Setter Property="Background" Value="#FFCEE4FF" />
    </Style>
    <Style x:Key="NormalLabelNoHeight" TargetType="Label" BasedOn="{StaticResource LabelStyleBaseNoHeight}">
        <Setter Property="Background" Value="#FFCEE4FF" />
    </Style>

    <Style TargetType="UserControl" x:Key="CsDate" BasedOn="{StaticResource UserControlBaseStyle}">
    </Style>

    <Style x:Key="TitleLabel" TargetType="Label" BasedOn="{StaticResource LabelStyleBase}">
        <Setter Property="Background" Value="#FF7BB7FF" />
    </Style>

    <Style x:Key="RequiredLabel" TargetType="Label" BasedOn="{StaticResource LabelStyleBase}">
        <!--<Setter Property="Background" Value="#FFFF997B" />-->
        <Setter Property="Background" Value="#FF7BB7FF" />
    </Style>

    <Style x:Key="SimpleLabel" TargetType="Label" BasedOn="{StaticResource LabelStyleBase}">
        <Setter Property="BorderThickness" Value="0"/>
    </Style>

    <Style x:Key="NormalTextBlock" TargetType="TextBlock" BasedOn="{StaticResource TextBlockStyleBase}">
    </Style>

    <Style x:Key="NormalPasswordBox" TargetType="PasswordBox" BasedOn="{StaticResource PasswordBoxStyleBase}">
    </Style>


    <!--#region DataGrid関連 -->

    <!-- ヘッダセルに対する書式設定
                 ・ヘッダ部は水平中央寄せ
            -->
    <Style x:Key="DataGridHeaderCellCenter" TargetType="DataGridColumnHeader">
        <Setter Property="HorizontalContentAlignment"  Value="Center"/>
    </Style>

    <!-- 各セルに対する書式設定
                 ・セルの垂直中央寄せ
            -->
    <Style x:Key="DataGridCellCenter" TargetType="DataGridCell" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type DataGridCell}">
                    <Grid Background="{TemplateBinding Background}">
                        <ContentPresenter VerticalAlignment="Center" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>

    </Style>

    <Style x:Key="NormalDataGrid" TargetType="DataGrid" BasedOn="{StaticResource DataGridStyleBase}">
        <Setter Property="RowHeight" Value="20"/>
        <Setter Property="ColumnHeaderStyle" Value="{StaticResource DataGridHeaderCellCenter}" />
        <Setter Property="CellStyle" Value="{StaticResource DataGridCellCenter}" />
    </Style>


    <Style x:Key="DataGridAutoRowHeight" TargetType="DataGrid" BasedOn="{StaticResource DataGridStyleBase}">
        <Setter Property="ColumnHeaderStyle" Value="{StaticResource DataGridHeaderCellCenter}" />
        <Setter Property="CellStyle" Value="{StaticResource DataGridCellCenter}" />
    </Style>

    <Style x:Key="DataGridCellElementStyleCenter" TargetType="{x:Type TextBlock}">
        <Setter Property="TextAlignment" Value="Center" />
    </Style>

    <Style x:Key="DataGridCellElementStyleLeft" TargetType="{x:Type TextBlock}">
        <Setter Property="TextAlignment" Value="Left" />
    </Style>

    <Style x:Key="DataGridCellElementStyleRight" TargetType="{x:Type TextBlock}">
        <Setter Property="TextAlignment" Value="Right" />
        <Setter Property="Margin" Value="0,0,5,0" />
    </Style>

    <!--#endregion DataGrid関連 -->

    <Style x:Key="SystemTitleTextBlock" TargetType="TextBlock" BasedOn="{StaticResource TextBlockStyleBase}">
        <Setter Property="FontFamily" Value="MS Gothic"/>
        <Setter Property="FontSize" Value="24"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
    </Style>


    <Style x:Key="TitleTextBlock" TargetType="TextBlock" BasedOn="{StaticResource TextBlockStyleBase}">
        <Setter Property="FontFamily" Value="MS Gothic"/>
        <Setter Property="FontSize" Value="13"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
    </Style>

    <Style x:Key="SupplementTextBlock" TargetType="TextBlock" BasedOn="{StaticResource TextBlockStyleBase}">
        <Setter Property="FontSize" Value="10"/>
        <Setter Property="Foreground" Value="#FF616161"/>
    </Style>

    <!--▽ 通常のテキストボックスのスタイル -->
    <Style x:Key="NormalTextBox" TargetType="TextBox" BasedOn="{StaticResource TextBoxStyleBase}">
    </Style>

    <!--▽ 読み取り専用のテキストボックスのスタイル -->
    <Style x:Key="ReadOnlyTextBox" TargetType="TextBox" BasedOn="{StaticResource TextBoxStyleBase}">
        <Setter Property="IsReadOnly" Value="True"/>
        <Setter Property="Background" Value="WhiteSmoke"/>
    </Style>

    <!--▽ 複数行のテキストボックスのスタイル -->
    <Style x:Key="MultiLineTextBox" TargetType="TextBox" BasedOn="{StaticResource TextBoxStyleBase}">
        <Setter Property="AcceptsReturn" Value="True"/>
        <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
        <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
        <Setter Property="VerticalAlignment" Value="Top"/>
        <Setter Property="VerticalContentAlignment" Value="Top"/>
        <Setter Property="TextWrapping" Value="Wrap"/>
        <Setter Property="Padding" Value="2"/>
    </Style>

    <Style x:Key="NormalButton" TargetType="Button" BasedOn="{StaticResource ButtonStyleBase}">
    </Style>

    <Style x:Key="MenuButton" TargetType="Button" BasedOn="{StaticResource ButtonStyleBase}">
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="Margin" Value="10"/>
    </Style>
    
    <Style x:Key="WindowStyle" TargetType="Window" BasedOn="{StaticResource WindowStyleBase}">
    </Style>

    <Style x:Key="SignInWindowStyle" TargetType="Window" BasedOn="{StaticResource WindowStyleBase}">
        <!--<Setter Property="Background">
                    <Setter.Value>
                        <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                            <LinearGradientBrush.GradientStops>
                                <GradientStop Offset="0.0" Color="#FFB0FFB0"/>
                                
                <GradientStop Offset="1.0" Color="#FFD6D6D6"/>
                
                                <GradientStop Offset="1.0" Color="#FFB6DAA2"/>
                            </LinearGradientBrush.GradientStops>
                        </LinearGradientBrush>
                    </Setter.Value>
            </Setter>-->
    </Style>


    <Style x:Key="GridSplitterStyle" TargetType="GridSplitter">
        <Setter Property="Background" Value="LightGray"></Setter>
        <Setter Property="ShowsPreview" Value="True"></Setter>
        <Setter Property="IsTabStop" Value="False"></Setter>
    </Style>


</ResourceDictionary>

[ProjectResource] プロジェクト(案件)ごとのスタイルを定義
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:SystemTemplate.resource"
                    xmlns:convreter="clr-namespace:SystemTemplate.resource.converter"
                    
                    >
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="ControlResource.xaml"/>
    </ResourceDictionary.MergedDictionaries>

    <convreter:StringToNumberConverter x:Key="stringToNumberConverter" />

    <!--#region Window -->
    <Style x:Key="LoginWindowStyle" TargetType="Window" BasedOn="{StaticResource WindowStyle}">
    </Style>

    <Style x:Key="MenuWindowStyle" TargetType="Window" BasedOn="{StaticResource WindowStyle}">
        <Setter Property="FontSize" Value="16"/>
        <Setter Property="Height" Value="740"/>
        <Setter Property="Width" Value="980"/>
    </Style>

    <Style x:Key="ShnWindowStyle" TargetType="Window" BasedOn="{StaticResource WindowStyle}">
        <Setter Property="Height" Value="740"/>
        <Setter Property="Width" Value="980"/>
    </Style>

    <!--#region ユーザーマスタ -->
    <Style x:Key="txtUserCDStyle" TargetType="TextBox" BasedOn="{StaticResource  NormalTextBox}">
        <Setter Property="MaxLength" Value="4"/>
        
        <!--数値入力のみとする-->
        <Setter Property="InputMethod.IsInputMethodEnabled" Value="False"/>
    </Style>

    <Style x:Key="txtPassWord" TargetType="TextBox" BasedOn="{StaticResource  NormalTextBox}">
        <Setter Property="MaxLength" Value="10"/>
    </Style>
     <!--#endregion-->

    <Style x:Key="txtTnkStyle" TargetType="TextBox" BasedOn="{StaticResource  NormalTextBox}">
        <Setter Property="MaxLength" Value="9"/>

        <Setter Property="InputMethod.IsInputMethodEnabled" Value="False"/>
        
        <!--カンマ区切りで小数点以下なし表示-->
        <Setter Property="Text" Value="{Binding Path=., Converter={StaticResource stringToNumberConverter}}" />

    </Style>

</ResourceDictionary>

[App.xaml]
<Application x:Class="SystemTemplate.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:SystemTemplate"
             StartupUri="win00_0000_Login.xaml">
    <Application.Resources>
        <ResourceDictionary>

            
            <!-- Static Resourceが設定されていないコントロールを判別するためのスタイル。個別でStaticResouceを設定していればそちらが上書きされる -->
            <Style TargetType="TextBox">
                <Setter Property="Background" Value="Red"/>
            </Style>

            <!--#region プロジェクトごとのコントロールを定義したリソース -->
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="resource\ProjectResource.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <!--#endregion-->
            
        </ResourceDictionary>


    </Application.Resources>
</Application>

WPFの画面表示速度について

TreeListViewの画面表示が遅かったのでいろいろ調べてみた。
結論的には、WPFが遅いんじゃなくて、私の使い方が間違っているだけだった。
WPFはいろんなことができるけど、使い方を間違っちゃうことが多いのでまだまだ場数が足りないのかなと思う。

250アイテムを表示するのに20秒以上かかっている。

どうやら画面表示が遅いので、いろいろと試してみた結果、TreeListViewにTextBoxを利用しているのが原因みたい。

TextBlockに置き換えることで比べ物にならないぐらい早くなった。
確かにTextBoxを250行×5列の数だけ表示していれば遅くなるのも当然な気がする。

常に編集状態にする必要はないのでExcelみたいに編集モードになった箇所だけTextBoxを表示して編集できるように修正をした。

Bool値をVisiblityに変換するConverterを定義し、普段はTextBlockを表示しておいて、IsSelectedプロパティがTrueになったときだけ、TextBoxのVisibilityをCollapsedからVisibleに変換する。

<GridViewColumn x:Name="gvcTitle" Header="タイトル" Width="200" HeaderContainerStyle="{StaticResource GridViewColumnHeaderStyle}">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <!-- Marginで左右から-6pxとしないとHearder部のタイトルとラインの位置がずれる.GridViewの既知の不具合 -->
            <Border BorderBrush="Black"  BorderThickness="0,0,0.5,0" Margin="-6,0,-6,0">
                <DockPanel>
                    <!--The Expander Button (can be used in any column (typically the first one))-->
                    <TreeListView:TreeListViewExpander Focusable="False" Visibility="{Binding TitleExpanderVisibility}"/>
                    <TreeListViewSubControls:TreeListItemTextBox Text="{Binding Data.Title, Mode=TwoWay}" VerticalContentAlignment="Center" VerticalAlignment="Stretch" Margin="2,2,2,2" HorizontalContentAlignment="Stretch"

                                                                 Style="{StaticResource EditTreeListTextBoxStyle}"  HorizontalAlignment="Stretch" BorderThickness="0" Opacity="1"  TabIndex="1" 

                                                                 Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DockPanel}}}" IsReadOnly="{Binding IsReadOnly}"

                                                                 Visibility="{Binding IsSelected,Converter={StaticResource boolToVisiblityConverter},ConverterParameter=True}"/>
                    <Border BorderThickness="0" Visibility="{Binding IsSelected,Converter={StaticResource boolToVisiblityConverter},ConverterParameter=False}" Margin="2,2,2,2" HorizontalAlignment="Stretch">
                        <TreeListViewSubControls:TreeListItemTextBlock Text="{Binding Data.Title, Mode=TwoWay}"  Style="{StaticResource TreeListTextBlockStyle}"  Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DockPanel}}}"  />
                    </Border>
                </DockPanel>
            </Border>
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>

PowerShellでWPFアプリケーションをBindingするときの注意点

 1.PowerShellでWPFアプリケーションのBindingについて マニアックだけどPowerShellでWPFアプリケーションを作っている。INotifyChangedを実装したいけど、PowerShellにはgetterやsetterがないので通知が発行できない。 そ...