☆JWWファイルの超簡易表示 その3

前回、前々回のTreeViewのチェックボックスとボールド表示を使ってレイヤーの表示/非表示部分を作ってみました。 例によってPeter.さんのjww data read & save unit Ver1.2βとAFsoftさんの「CAD作ろ」のコードを使わせてもらっています。

Jww


unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    TreeView1: TTreeView;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormMouseWheelDown(Sender: TObject; Shift: TShiftState;
      MousePos: TPoint; var Handled: Boolean);
    procedure FormMouseWheelUp(Sender: TObject; Shift: TShiftState;
      MousePos: TPoint; var Handled: Boolean);
    procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure PaintBox1Paint(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure TreeView1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    ox, oy: Integer;                   // 原点
    ImageWidth,ImageHeight: Integer;   // 画面ドットサイズ = PaintBox1のサイズ
    PaperWidth, PaperHeight: Double;   // 用紙サイズ
    mm_dot: Double;                    // mm_dot比
    view_x1, view_y1, view_x2, view_y2: Double;  //ビューエリア
    DragStart_X, DragStart_Y: Integer; // ドラッグ開始点
    DragEnd_X, DragEnd_Y: Integer;     // ドラッグ終了点
    DragModeKind : Integer;            // ドラッグモード 0:移動 1:拡大 2:縮小
    DraggingNow : Boolean;             // ドラッグ中かどうかのフラグ
    RubberBandShow : Boolean;          // ラバーバンド表示フラグ
    procedure Calc_mm_dot;             // mm_dot比の計算
    procedure Calc_ViewArea;           // ビューエリアの計算
    procedure CLS;                     // 画面を消す
    procedure Draw_Paper_Frame;        // 用紙枠の描画
    procedure Drawdata;                // 図面の描画
    procedure RubberBand(x1, y1, x2, y2:Integer); // ラバーバンドの描画
    procedure SetPaperSize(PaperSize: Integer);   // 用紙サイズの設定
    procedure SetLayerName;                       // レイヤー名の設定
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  Commctrl, jwwunit;

const
  TVIS_CHECKED  = $2000;

// 初期設定
procedure TForm1.FormCreate(Sender: TObject);
var
  FileName: String;
  WindowStyles: Integer;
begin
  // TreeViewにCheckBoxを表示させます。
  WindowStyles := GetWindowLong(TreeView1.Handle, GWL_STYLE);
  SetWindowLong(TreeView1.Handle, GWL_STYLE, WindowStyles or TVS_CHECKBOXES);

  // 初期化
  DraggingNow := False;
  RubberBandShow := False;

  // ファイルの読み込み
  FileName := 'c:\jww\Aマンション平面例.jww';

  JWWBlockList := TJWWBlockList.Create;

  // JWWファイル読込み
  jwwRead(FileName);

  // 用紙サイズの設定
  SetPaperSize(JWWHd.m_nZumen);

  // mm_dot比の計算
  Calc_mm_dot;

  // ビューエリアの計算
  Calc_ViewArea;

  // レイヤー名及び表示/非表示の設定
  SetLayerName;
end;

// 終了処理
procedure TForm1.FormDestroy(Sender: TObject);
begin
  JWWBlockList.Free;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  // mm_dot比の計算
  Calc_mm_dot;

  // ビューエリアの計算
  Calc_ViewArea;
end;

// 用紙サイズの設定
{
  Paramater 用紙サイズ 
  0-A0
  1-A1
  2-A2
  3-A3
  4-A4
  上記以外は、A0サイズとする。サイズを調べるのが面倒だから(^^;
}
procedure TForm1.SetPaperSize(PaperSize: Integer); // 用紙サイズの設定
begin
  case PaperSize of
    0: begin //A0
         PaperWidth  :=1189;
         PaperHeight := 841;
       end;
    1: begin //A1
         PaperWidth  := 841;
         PaperHeight := 594;
       end;
    2: begin //A2
         PaperWidth  := 594;
         PaperHeight := 420;
       end;
    3: begin //A3
         PaperWidth  := 420;
         PaperHeight := 297;
       end;
    4: begin //A4
         PaperWidth  := 297;
         PaperHeight := 210;
       end;
  else
    PaperWidth  :=1189;
    PaperHeight := 841;
  end;
end;

// mm_dot比の計算
procedure TForm1.Calc_mm_dot;
var
  P1, P2: Double;
begin
  ImageWidth  := PaintBox1.Width;
  ImageHeight := PaintBox1.Height;
  P1 := ImageWidth  / PaperWidth;
  P2 := ImageHeight / PaperHeight;

  if (P1 < P2) then
    mm_dot := P1
  else
    mm_dot := P2;
end;

// ビューエリアの計算
procedure TForm1.Calc_ViewArea;
begin
  view_x1 := - (ImageWidth  / 2.0) / mm_dot;
  view_y1 := - (ImageHeight / 2.0) / mm_dot;
  view_x2 :=   (ImageWidth  / 2.0) / mm_dot;
  view_y2 :=   (ImageHeight / 2.0) / mm_dot;
end;

//画面の消去
procedure TForm1.CLS;
begin
  PaintBox1.Canvas.Pen.Mode  := pmCopy;
  PaintBox1.Canvas.Pen.Color := clWhite;
  PaintBox1.Canvas.Pen.Style := psSolid;
  PaintBox1.Canvas.Pen.Width := 1;

  PaintBox1.Canvas.Brush.Color := clWhite;
  PaintBox1.Canvas.Brush.Style := bsSolid;
  PaintBox1.Canvas.Rectangle(0, 0, ImageWidth, ImageHeight);
end;

//用紙枠の描画
procedure TForm1.Draw_Paper_Frame;
var
  x1, y1, x2, y2: Integer;
begin
  // 原点の計算
  ox := Round( (0.0 - view_x1) * mm_dot);
  oy := Round(-(0.0 - view_y2) * mm_dot);
  
  // 画面の消去
  CLS;

  // 用紙枠の描画
  x1 := Round( (-PaperWidth /2 - view_x1) * mm_dot);
  y1 := Round(-(-PaperHeight/2 - view_y2) * mm_dot);
  x2 := Round( ( PaperWidth /2 - view_x1) * mm_dot);
  y2 := Round(-( PaperHeight/2 - view_y2) * mm_dot);
  PaintBox1.Canvas.Pen.Color := clBlue;
  PaintBox1.Canvas.Brush.Style := bsClear;
  PaintBox1.Canvas.Rectangle(x1, y1, x2, y2);
end;

procedure TForm1.FormMouseWheelDown(Sender: TObject; Shift: TShiftState;
  MousePos: TPoint; var Handled: Boolean);
var
  cx, cy: Double;
  dx, dy: Double;
var
  Pt: TPoint;
begin
  GetCursorPos(Pt);
  Pt := ScreenToClient(Pt);
  if ((Pt.X > 0) and (Pt.X < PaintBox1.Width)) and
     ((Pt.Y > 0) and (Pt.Y < PaintBox1.Height)) then
  begin
    Handled := True;

    //2倍に拡大
    cx := (view_x1 + view_x2) / 2.0;  // ビュー中央のX座標
    cy := (view_y1 + view_y2) / 2.0;  // ビュー中央のY座標
    dx := Abs(view_x2 - view_x1);
    dy := Abs(view_y2 - view_y1);
    mm_dot := mm_dot * 2.0;
    view_x1 := cx - (dx / 4.0);
    view_y1 := cy - (dy / 4.0);
    view_x2 := cx + (dx / 4.0);
    view_y2 := cy + (dy / 4.0);
    Drawdata;
  end;
end;

procedure TForm1.FormMouseWheelUp(Sender: TObject; Shift: TShiftState;
  MousePos: TPoint; var Handled: Boolean);
var
  cx, cy: Double;
  dx, dy: Double;
var
  Pt: TPoint;
begin
  GetCursorPos(Pt);
  Pt := ScreenToClient(Pt);
  if ((Pt.X > 0) and (Pt.X < PaintBox1.Width)) and
     ((Pt.Y > 0) and (Pt.Y < PaintBox1.Height)) then
  begin
    Handled := True;

    // 1/2倍に縮小
    cx := (view_x1 + view_x2) / 2.0;  // ビュー中央のX座標
    cy := (view_y1 + view_y2) / 2.0;  // ビュー中央のY座標
    dx := Abs(view_x2 - view_x1);
    dy := Abs(view_y2 - view_y1);
    mm_dot := mm_dot * 0.5;
    view_x1 := cx - dx;
    view_y1 := cy - dy;
    view_x2 := cx + dx;
    view_y2 := cy + dy;
    Drawdata;
  end;
end;

procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (ssLeft in Shift) or (ssRight in Shift) then
  begin
    DraggingNow := True;
    DragStart_X := X;
    DragStart_Y := Y;
    RubberBandShow := False;
    DragEnd_X := 0;
    DragEnd_Y := 0;

    if (ssLeft  in Shift) then
      DragEnd_X := 1;

    if (ssRight in Shift) then
      DragEnd_Y := 1;
  end;
end;

procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if (DraggingNow) then
  begin
    if (RubberBandShow) then
      RubberBand(DragStart_X, DragStart_Y, DragEnd_X, DragEnd_Y);

    if (ssLeft in Shift) and (ssRight in Shift) then
    begin
      RubberBand(DragStart_X, DragStart_Y, X, Y);
      RubberBandShow := True;
      DragEnd_X := X;
      DragEnd_Y := Y;
    end;
  end;
end;

procedure TForm1.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  x1, y1, x2, y2: Double;
  cx, cy: Double;
  dx, dy: Double;
  bx, by: Double;
  P1, P2: Double;
begin
  if (DraggingNow) then
  begin
    // ドラッグ処理の終了
    DraggingNow := False;
    if (RubberBandShow) then
      RubberBand(DragStart_X, DragStart_Y, DragEnd_X, DragEnd_Y);

    if (Abs(DragStart_X - X) <= 5) and (Abs(DragStart_Y - Y) <= 5) then
       DragModeKind := 0     // 移動
    else
    begin
      if (DragStart_X <= X) then
        DragModeKind := 1  // 拡大
      else
        DragModeKind := 2; // 縮小
    end;

    x1 := view_x1 + (DragStart_X / mm_dot);
    y1 := view_y2 - (DragStart_Y / mm_dot);
    x2 := view_x1 + (X / mm_dot);
    y2 := view_y2 - (Y / mm_dot);
    dx := Abs(view_x2 - view_x1);
    dy := Abs(view_y2 - view_y1);
    bx := Abs(x1 - x2);
    by := Abs(y1 - y2);
    cx := (x1 + x2) / 2.0;
    cy := (y1 + y2) / 2.0;

    case (DragModeKind) of
      0: begin    // 移動
           cx := (view_x1 +view_x2) / 2.0;
           cy := (view_y1 +view_y2) / 2.0;
           dx := x2 - cx;
           dy := y2 - cy;
           view_x1 := view_x1 + dx;
           view_y1 := view_y1 + dy;
           view_x2 := view_x2 + dx;
           view_y2 := view_y2 + dy;
         end;
      1: begin    // 拡大
           P1 := dx / bx;
           P2 := dy / by;

           if (P1 < P2) then
             mm_dot := mm_dot * P1
           else
             mm_dot := mm_dot * P2;

           view_x1 := cx - ((ImageWidth  / 2.0) / mm_dot);
           view_y1 := cy - ((ImageHeight / 2.0) / mm_dot);
           view_x2 := cx + ((ImageWidth  / 2.0) / mm_dot);
           view_y2 := cy + ((ImageHeight / 2.0) / mm_dot);
         end;
      2: begin    // 縮小
           P1 := bx / dx;
           P2 := by / dy;

           if (P1 > P2) then
             mm_dot := mm_dot * P1
           else
             mm_dot := mm_dot * P2;

           view_x1 := cx - ((ImageWidth  / 2.0) / mm_dot);
           view_y1 := cy - ((ImageHeight / 2.0) / mm_dot);
           view_x2 := cx + ((ImageWidth  / 2.0) / mm_dot);
           view_y2 := cy + ((ImageHeight / 2.0) / mm_dot);
         end;
    end;
    Drawdata;
  end
end;

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  Drawdata;
end;

// ラバーバンド長方形描画
procedure TForm1.RubberBand(x1, y1, x2, y2:Integer);
begin
  PaintBox1.Canvas.Pen.Mode := pmXor;
  PaintBox1.Canvas.Pen.Color := clAqua;
  PaintBox1.Canvas.Pen.Width := 0;
  PaintBox1.Canvas.Pen.Style := psSolid;
  PaintBox1.Canvas.Brush.Style := bsClear;
  PaintBox1.Canvas.Rectangle(x1,y1,x2,y2);
end;

// CADデータの描画
procedure TForm1.Drawdata;

  // 座標を変換
  procedure Change_mm_dot(x1, x2, y1, y2: Double;
    var ix1, ix2, iy1, iy2: Integer);
  begin
    ix1 := Round( x1 * mm_dot + ox );
    iy1 := Round(-y1 * mm_dot + oy );
    ix2 := Round( x2 * mm_dot + ox );
    iy2 := Round(-y2 * mm_dot + oy );
  end;

  // 線及び円弧の線種・線色の設定
  procedure LineSetting(Root: CData);
  begin
    // ペンの太さ
    PaintBox1.Canvas.Pen.width := 0;
    // ペンのスタイル
    case Root.m_nPenStyle of
      1: PaintBox1.Canvas.Pen.Style := psSolid;
      2,3,4,9: PaintBox1.Canvas.Pen.Style := psDot;
      5,6,7,8: PaintBox1.Canvas.Pen.Style := psDashDot;
    end;
    // ペンの色
    case Root.m_nPenColor of
      1: PaintBox1.Canvas.Pen.Color := RGB(0,192,192);
      2: PaintBox1.Canvas.Pen.Color := clBlack;
      3: PaintBox1.Canvas.Pen.Color := RGB(0,192,0);
      4: PaintBox1.Canvas.Pen.Color := RGB(192,192,0);
      5: PaintBox1.Canvas.Pen.Color := RGB(192,0,192);
      6: PaintBox1.Canvas.Pen.Color := RGB(0,0,255);
      7: PaintBox1.Canvas.Pen.Color := RGB(0,128,128);
      8: PaintBox1.Canvas.Pen.Color := RGB(255,0,128);
      9: PaintBox1.Canvas.Pen.Color := RGB(255,128,255);
    end;
  end;

  // フォントの設定
  procedure FontSetting(ACDataMoji: CDataMoji);
  begin
    // フォント名
    PaintBox1.Canvas.Font.Name := ACDataMoji.m_strFontName;
    // フォントの高さ
    PaintBox1.Canvas.Font.Height := Round(ACDataMoji.m_dSizeY * mm_dot);
    // フォントの色
    case JWWHd.m_Moji[ACDataMoji.m_nMojiShu].m_anMojiCol of
      1: PaintBox1.Canvas.Font.Color := RGB(0,192,192);
      2: PaintBox1.Canvas.Font.Color := clBlack;
      3: PaintBox1.Canvas.Font.Color := RGB(0,192,0);
      4: PaintBox1.Canvas.Font.Color := RGB(192,192,0);
      5: PaintBox1.Canvas.Font.Color := RGB(192,0,192);
      6: PaintBox1.Canvas.Font.Color := RGB(0,0,255);
      7: PaintBox1.Canvas.Font.Color := RGB(0,128,128);
      8: PaintBox1.Canvas.Font.Color := RGB(255,0,128);
      9: PaintBox1.Canvas.Font.Color := RGB(255,128,255);
    end;
  end;

  // レイヤーの表示/非表示
  function LayerCheck(Root: CData): Boolean;
  var
    I, J: Integer;
    F1, F2: Boolean;
  begin
    I := Root.m_nGLayer; // レイヤーグループ
    J := Root.m_nLayer;  // レイヤー
    F1 := JWWHd.GLay[I].m_anGLay <> 0;
    F2 := JWWHd.GLay[I].m_nLay[J].m_aanLay <> 0;
    Result := F1 and F2;
  end;

  // 補助線かどうか
  function IsSupportLine(Root: CData): Boolean;
  begin
    Result := (Root.m_nPenStyle = 9);
  end;

var
  I: Integer;
  x1, x2, y1, y2, Hankei: Double;
  ix1, ix2, iy1, iy2: Integer;
  Moji: String;
  Hajime, Owari: Double;
  wx1,wy1,wx2,wy2 : Double ;
  ax3,ay3,ax4,ay4 : integer ;
  LF: TLogFont;
begin
  // 用紙枠の描画
  Draw_Paper_Frame;

  // 線の描画
  PaintBox1.Canvas.Pen.Color := clBlack;
  for I := Low(JWWSen) to High(JWWSen) do
  begin
    if (not IsSupportLine(JWWSen[I].Root)) and LayerCheck(JWWSen[I].Root) then
    begin
      x1 := JWWSen[I].m_start_x;
      y1 := JWWSen[I].m_start_y;
      x2 := JWWSen[I].m_end_x;
      y2 := JWWSen[I].m_end_y;

      // mm→dot変換
      Change_mm_dot(x1, x2, y1, y2, ix1, ix2, iy1, iy2);

      // 線の設定
      LineSetting(JWWSen[I].Root);

      //線の描画
      PaintBox1.Canvas.MoveTo(ix1, iy1);
      PaintBox1.Canvas.LineTo(ix2, iy2);
    end;
  end;

  // 円弧の描画
  for I := Low(JWWEnko) to High(JWWEnko) do
  begin
    if (not IsSupportLine(JWWEnko[I].Root)) and LayerCheck(JWWEnko[I].Root) then
    begin
      x1 := JWWEnko[I].m_start_x;
      y1 := JWWEnko[I].m_start_y;
      Hankei := JWWEnko[I].m_dHankei;
      Hajime := JWWEnko[I].m_radKaishiKaku + JWWEnko[I].m_radKatamukiKaku;
      Owari :=  JWWEnko[I].m_radKaishiKaku + JWWEnko[I].m_radEnkoKaku +
        JWWEnko[I].m_radKatamukiKaku;
      if JWWEnko[I].m_radKatamukiKaku < 0 then
      begin
        wx1 := X1-Hankei*JWWEnko[I].m_dHenpeiRitsu;
        wy1 := Y1-Hankei;
        wx2 := X1+Hankei*JWWEnko[I].m_dHenpeiRitsu;
        wy2 := Y1+Hankei;
      end
      else
      begin
        wx1 := X1-Hankei;
        wy1 := Y1-Hankei*JWWEnko[I].m_dHenpeiRitsu;
        wx2 := X1+Hankei;
        wy2 := Y1+Hankei*JWWEnko[I].m_dHenpeiRitsu;
      end;

      // 円弧の設定
      LineSetting(JWWEnko[I].Root);

      // mm→dot変換
      Change_mm_dot(wx1, wx2, wy1, wy2, ix1, ix2, iy1, iy2);

      // 円弧の描画
      if JWWEnko[I].m_bZenEnFlg = 1 then
        PaintBox1.Canvas.Ellipse(ix1, iy1,ix2,iy2)  //円
      else
      begin
        ax3 := Round( ((x1+Hankei*Cos(Hajime)))*MM_dot+ox);
        ay3 := Round(-((y1+Hankei*Sin(Hajime)))*MM_dot+oy);
        ax4 := Round( ((x1+Hankei*Cos(Owari )))*MM_dot+ox);
        ay4 := Round(-((y1+Hankei*Sin(Owari )))*MM_dot+oy);

        if (ax3 <> ax4) or (ay3 <> ay4) then
        begin
          if (JWWEnko[I].m_radEnkoKaku < 0) then
            PaintBox1.Canvas.Arc(ix1,iy1,ix2,iy2,ax4,ay4,ax3,ay3)
          else
            PaintBox1.Canvas.Arc(ix1,iy1,ix2,iy2,ax3,ay3,ax4,ay4);
        end;
      end;
    end;
  end;

  // 文字の描画
  for I := Low(JWWMoji) to High(JWWMoji) do
  begin
    if LayerCheck(JWWMoji[I].Root) then
    begin
      x1 := JWWMoji[I].m_start_x;
      y1 := JWWMoji[I].m_start_y;
      x2 := JWWMoji[I].m_end_x;
      y2 := JWWMoji[I].m_end_y;
      Moji := JWWMoji[I].m_string;

      // mm→dot変換
      Change_mm_dot(x1, x2, y1, y2, ix1, ix2, iy1, iy2);

      // フォントの設定
      FontSetting(JWWMoji[I]);

      // 文字の描画
      GetObject(PaintBox1.Canvas.Font.Handle, SizeOf(LF), @LF);
      SetTextAlign(PaintBox1.Canvas.Handle,TA_LEFT or TA_BOTTOM);
      LF.lfEscapement := Round(JWWMoji[I].m_degKakudo*10);
      PaintBox1.Canvas.Font.Handle := CreateFontIndirect(LF);
      PaintBox1.Canvas.TextOut(ix1,iy1,moji);
    end;
  end;
end;


// レイヤー名及び表示/非表示の設定
procedure TForm1.SetLayerName;

  // ノードのチェックボックス設定
  procedure NodeCheckBox(Node: TTreeNode; F: Boolean);
  var
    TvItem: TTVItem;
  begin
    TvItem.hItem := Node.ItemId;
    TvItem.stateMask := TVIS_STATEIMAGEMASK;
    TvItem.mask := TVIF_HANDLE or TVIF_STATE;
    TvItem.state := TVIS_CHECKED;
    TreeView_SetItem(TreeView1.Handle, TvItem);
  end;

  // 太字設定
  procedure SetBold(Node: TTreeNode);
  var
    TvItem: TTVItem;
  begin
    TvItem.hItem := Node.ItemId;
    TvItem.stateMask := TVIS_BOLD;
    TvItem.mask := TVIF_HANDLE or TVIF_STATE;
    TvItem.state := TVIS_BOLD;
    TreeView_SetItem(TreeView1.Handle, TvItem);
  end;

var
  I, J, K, SNo, ENo: Integer;
  NodeA, NodeB: TTreeNode;
  LayerNo, Scale, LayerName: String;
  F: Boolean;
begin
  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Clear;
    // レイヤーグループの設定
    for I := Low(JWWHd.m_aStrGLayName) to High(JWWHd.m_aStrGLayName) do
    begin
      // レイヤーグループ番号
      LayerNo := '[' + IntToHex(I, 1) +'] ';
      // 縮尺
      if JWWHd.GLay[I].m_adScale < 1 then
        Scale := IntToStr(Round(1 / JWWHd.GLay[I].m_adScale)) + '/1'
      else
        Scale := '1/'+ IntToStr(Trunc(JWWHd.GLay[I].m_adScale));

      // レイヤーグループ名
      LayerName := ' ' + JWWHd.m_aStrGLayName[I];

      // ノードの追加及び表示/非表示の設定
      NodeA := TreeView1.Items.Add(nil, LayerNo + Scale + LayerName);
      F := JWWHd.GLay[I].m_anGLay <> 0;
      NodeCheckBox(NodeA, F);
      SetBold(NodeA);

      // レイヤーの設定
      SNo := I * 16;   // 読み取り開始番号
      ENo := SNo + 15; // 読み取り終了番号
      for J := SNo to ENo do
      begin
        K := J - SNo;
        LayerNo := '[' + IntToHex(K, 1) +'] ';       // レイヤーグループ番号
        LayerName := ' ' + JWWHd.m_aStrLayName[J];   // レイヤー名
        // ノードの追加及び表示/非表示の設定
        NodeB := TreeView1.Items.AddChild(NodeA, LayerNo + LayerName);
        F := JWWHd.GLay[I].m_nLay[K].m_aanLay <> 0;
        NodeCheckBox(NodeB, F);
      end;
    end;
  finally
    TreeView1.Items.EndUpdate;
  end;
end;

procedure TForm1.TreeView1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  lpht: TTVHitTestInfo;
  Node: TTreeNode;
  I,J, K: Integer;
begin
  // クリックした位置のノードを取得します。
  Node := TreeView1.GetNodeAt(X, Y);
  if Node = nil then Exit;

  // クリックした位置がチェックボックス上からどうかを調べます。
  lpht.pt.x := X;
  lpht.pt.y := Y;
  TreeView_HitTest(TreeView1.Handle, lpht);

  if lpht.flags = TVHT_ONITEMSTATEICON then
  begin
    if TreeView_GetCheckState(TreeView1.Handle, Node.ItemId) > 0 then
      K := 1
    else
      K := 0;

    if Node.Level = 0 then
    begin
      I := Node.AbsoluteIndex mod 16;
      JWWHd.GLay[I].m_anGLay := K;
    end
    else
    begin
      I := Node.Parent.AbsoluteIndex mod 16;
      J := Node.Index;
      JWWHd.GLay[I].m_nLay[J].m_aanLay := K;
    end;
    PaintBox1.Invalidate;
  end;
end;

end.


[20071210NodeCheckBox手続きを訂正]
今、ふと見るとパラメーターFが無視されてました。下記のように訂正します。
  // ノードのチェックボックス設定
  procedure NodeCheckBox(Node: TTreeNode; F: Boolean);
  var
    TvItem: TTVItem;
  begin
    TvItem.hItem := Node.ItemId;
    TvItem.stateMask := TVIS_STATEIMAGEMASK;
    TvItem.mask := TVIF_HANDLE or TVIF_STATE;
    if F then
      TvItem.state := TVIS_CHECKED
    else
      TvItem.state := TVIS_CHECKED shr 1;
    TreeView_SetItem(TreeView1.Handle, TvItem);
  end;
あるいは、パラメーターFを削除して、レイヤーを表示する場合のみ呼び出した方がいいのかも知れませんね。 (チェックボックスを設定した時には、チェックが外れているから)

|

☆TreeViewのノードを太字にする。

TreeViewのチェックボックスを設定する処理を模索していたときに見つけたTVIS_BOLD。 これを使うと、以下の処理で確かに太字になるのですが、使い方が正しいかどうかは不明です(^^;

uses CommCtrl;

procedure TForm1.FormCreate(Sender: TObject);

  procedure SetBold(Node: TTreeNode);
  var
    TvItem: TTVItem;
  begin
    TvItem.hItem := Node.ItemId;
    TvItem.stateMask := TVIS_BOLD;
    TvItem.mask := TVIF_HANDLE or TVIF_STATE;
    TvItem.state := TVIS_BOLD;
    TreeView_SetItem(TreeView1.Handle, TvItem);
  end;

var
  I: Integer;
  Node: TTreeNode;
begin
  for I := 0 to 10 do
  begin
    Node := TreeView1.Items.Add(nil, 'Item'+IntToStr(I));
    SetBold(Node);
  end;
end;

|

☆TreeViewのチェックボックスを設定する。

☆TreeViewにチェックボックスを表示する。では、 マウスによりチェックボックスを設定しましたが、 今回はプログラムからの設定を試してみました。

// チェックする
procedure TForm1.Button1Click(Sender: TObject);
var
  TvItem: TTVItem;
  Node: TTreeNode;
begin
  Node := TreeView1.Selected;
  if Node = nil then Exit;

  TvItem.hItem := Node.ItemId;
  TvItem.stateMask := TVIS_STATEIMAGEMASK;
  TvItem.mask := TVIF_HANDLE or TVIF_STATE;
  TvItem.state := TVIS_CHECKED;
  TreeView_SetItem(TreeView1.Handle, TvItem);
end;

// チェックを外す
procedure TForm1.Button2Click(Sender: TObject);
var
  TvItem: TTVItem;
  Node: TTreeNode;
begin
  Node := TreeView1.Selected;
  if Node = nil then Exit;

  TvItem.hItem := Node.ItemId;
  TvItem.stateMask := TVIS_STATEIMAGEMASK;
  TvItem.mask := TVIF_HANDLE or TVIF_STATE;
  TvItem.state := TVIS_CHECKED shr 1;
  TreeView_SetItem(TreeView1.Handle, TvItem);
end;

// チェックされているかどうかを調べる
procedure TForm1.Button3Click(Sender: TObject);
var
  Node: TTreeNode;
begin
  Node := TreeView1.Selected;
  if Node = nil then Exit;

  if TreeView_GetCheckState(TreeView1.Handle, Node.ItemId) > 0 then
    ShowMessage('Checked')
  else
    ShowMessage('Not Checked');
end;

|

☆TreeViewのアイテム高さを設定する。

TreeViewでは、使うアイコンによってノードの間隔がとても狭くなることがありますが、 そのような場合に使える処理です。

uses
  CommCtrl;

procedure SetTreeNodeHeight(TreeView: TTreeView; Height: Integer);
var
  H: Integer;
begin
  H := TreeView.Perform(TVM_GETITEMHEIGHT, 0, 0);
  if H <> Height then
    TreeView.Perform(TVM_SETITEMHEIGHT, Height, 0);
end;

※関連付けられたImageListのサイズによっても、ノードの高さは変わります。

|

☆TreeViewにチェックボックスを表示する。

あまり使うことがないのですが、TreeViewにチェックボックスを表示させてみました。
チェックボックスを利用するには、ウィンドウスタイルの設定だけでいいと思うのですが、同じチェックボックスを何度かクリックしたときの応答がいまいちのような気がします。この点については、よくわかりませんでした。
今回、チェックボックスがクリックされたタイミングで、設定状態を取得する処理も書いてみました。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    StatusBar1: TStatusBar;
    CheckBox1: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure TreeView1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  Commctrl;

const
  TVIS_CHECKED  = $2000;

// ウィンドウスタイルの設定---チェックボックスを設定します。
procedure TForm1.FormCreate(Sender: TObject);
var
  WindowStyles: Integer;
  I: Integer;
begin
  WindowStyles := GetWindowLong(TreeView1.Handle, GWL_STYLE);
  SetWindowLong(TreeView1.Handle, GWL_STYLE, WindowStyles or TVS_CHECKBOXES);
  StatusBar1.SimplePanel := True;

  // サンプルデータ
  for I := 1 to 9 do
    TreeView1.Items.Add(nil, 'Item' + IntToStr(I));
end;

// チェックボックスがクリックされたことを取得します。
procedure TForm1.TreeView1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  lpht: TTVHitTestInfo;
  TvItem: TTVItem;
  Node: TTreeNode;
  Check: Boolean;
begin
  // クリックした位置のノードを取得します。
  Node := TreeView1.GetNodeAt(X, Y);
  if Node = nil then Exit;

  // クリックした位置がチェックボックス上からどうかを調べます。
  lpht.pt.x := X;
  lpht.pt.y := Y;
  TreeView_HitTest(TreeView1.Handle, lpht);

  if lpht.flags = TVHT_ONITEMSTATEICON then
  begin
    // チェックボックス上をクリックされた場合の処理

    // チェックされているかどうかを調べます。
    TvItem.Mask := TVIF_STATE;
    TvItem.hItem := Node.ItemId;
    TreeView_GetItem(TreeView1.Handle, TvItem);
    Check := (TvItem.State and TVIS_CHECKED) > 0;

    if Check then
      StatusBar1.SimpleText := 'Checked'
    else
      StatusBar1.SimpleText := 'Unchecked';

    // チェックボックスをクリックした時に、ノードを移動させます。
    TreeView1.Selected := Node;
  end;
end;

end.

参考にしたサイト

MSDN
Tree-View Controls

Microsoft サポートオンライン
[HOWTO] TreeView コントロールでユーザーがチェック ボックスをクリックしたときに通知を受け取る方法

|

☆TreeViewのお手軽なUndo処理

前回のTreeViewのDrag&Dropのコードを使って、お手軽なUndo処理(手抜き?)を紹介します。
一番お手軽なのは、SaveToFile、LoadFromFileを使って一時ファイルでやり取りする方法です。しかしこれでは、展開状態を別に保存する必要があり面倒です。そこでもう一つTreeViewを用意してクローンを作成することにします。

まず、前回のコードの中で、TreeView1DragDropを次のように修正します。(2行追加するだけ)
procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  srcNode, dstNode: TTreeNode;
  HT: THitTests;
begin
  // Explorer等は、ファイル名でソートされていますが、
  // ソートではなく意図したノードの順番に移動させたい場合があります。
  // そのような時に私のアプリでは、
  // アイコンで挿入移動、文字列で子追加移動という仕様にしています。
  // これって結構便利だと思うんですけど、あまり採用されてないみたい。

  srcNode :=TreeView1.Selected;           // 選択ノード
  dstNode := TreeView1.DropTarget;        // ドロップターゲットノード

  // Undo用TreeViewに登録させます。       //←追加
  UndoRegist;                             //←追加

  HT := TreeView1.GetHitTestInfoAt(X, Y);
  if (htOnIcon in HT) then
    srcNode.MoveTo(dstNode, naInsert)     // アイコンを選択している場合
  else
    srcNode.MoveTo(dstNode, naAddChild);  // 項目を選択している場合
end;

次にUndoの処理です。
フォームにTreeViewを1つ、SpeedButtonを2つ追加します。
そして、次のコードを最後に追加します。
var
  idx1: Integer;
  CanUndo: Boolean = False;
  // TopIndex: Integer; // (A)

// Undo用TreeViewに登録させます。
procedure TForm1.UndoRegist;
var
  I: Integer;
  F: Boolean;
begin
  TreeView1.Items.BeginUpdate;
  try
    // TopIndex := TreeView1.TopItem.AbsoluteIndex; // (A)

    if TreeView1.Selected = nil then
      idx1 := -1
    else
      idx1 := TreeView1.Selected.AbsoluteIndex;

    TreeView2.Items.Assign(TreeView1.Items);

    // 展開状態の保存
    for I := TreeView1.Items.Count -1 downto 0 do
    begin
      F := TreeView1.Items[I].Expanded;
      if TreeView2.Items[I].Expanded <> F then
        TreeView2.Items[I].Expanded := True;
    end;
    CanUndo := True;
  finally
    TreeView1.Items.EndUpdate;
  end;
end;

// Undo
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  I: Integer;
  F: Boolean;
begin
  // Undoできない場合の処理
  if not CanUndo then Exit;

  TreeView1.Items.BeginUpdate;
  try
    TreeView1.Items.Assign(TreeView2.Items);

    // 展開状態の再現
    for I := TreeView2.Items.Count -1 downto 0 do
    begin
      F := TreeView2.Items[I].Expanded;
      if TreeView1.Items[I].Expanded <> F then
        TreeView1.Items[I].Expanded := True;
    end;

    if idx1 >= 0 then
      TreeView1.Items[idx1].Selected := True;
    TreeView1.Selected.MakeVisible; // (B)

    CanUndo := False;
  finally
    TreeView1.Items.EndUpdate;
    // TreeView1.TopItem := TreeView1.Items[TopIndex]; // (A)
  end;
end;

// Delete
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  if TreeView1.Selected <> nil then
  begin
    // Undo用TreeViewに登録させます。
    UndoRegist;
    TreeView1.Selected.Delete;
  end;
end;


あとは実行するだけです。移動や削除をしてからUndoボタンを押すと、Undoできます。処理前にUndoRegistを呼び出せば、追加、挿入時もこのUndo機能が使えます。 Undo後は、MakeVisibleで、Undo前の選択ノード表示していますが、TopItemにこだわるのであれば、(B)をコメントアウトして、(A)を有効にします。 TopItemへの設定は、TreeView1.Items.BeginUpdate~EndUpdate内では無効みたいなので、ちょっとちらつきます。

TreeViewのアイテム数にもよりますが、最近のマシンでは、これぐらいのメモリの無駄は問題ないと思います。しかし、これはあくまで一つのサンプルなので、きちんとしたアプリでは、クラスとしてUndoList、RedoListを作成する 、ファイルを利用する等で、リソースが「MOTTAINAI」と言われないような処理を心掛けたいですね(笑)

[2007/08/04 am10:54 全面的に訂正しました]
展開状態を設定し忘れていたからです(^^;

|

☆TreeViewのDrag&Drop

TreeView Drag drop delphi・・・このキーワードでここに来られる方がおられます。以前に .NET(Windowsフォームアプリケーション)での処理を書いていますので、それが該当するんでしょうね。
でもおそらくWIN32での処理をお探しだと思いますので、その処理を書きたいと思います。

簡単に説明すると「TreeView1.OnDragDropで Node の Selected、DropTarget、MoveTo を使って処理するだけです。」と書いてしまえばいいんでしょうが、せっかくなので縮小ノードの展開及び非表示部分のスクロール処理、並びにターゲットの選択部分によるノードの移動処理を含めたサンプルにしたいと思います。

でもTreeViewは奥深いので、Node.DataやOnChange等が原因で様々な問題が起こります。又、DragModeをdmAutomaticで使うとクリックしたかっただけなのに、ノード移動させてしまったという問題も比較的よく起こります。くれぐれもご注意を(笑)

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, ImgList, ExtCtrls;

type
  TForm1 = class(TForm)
    TreeView1: TTreeView;
    ImageList1: TImageList;
    Timer1: TTimer;
    Timer2: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure TreeView1EndDrag(Sender, Target: TObject; X, Y: Integer);
    procedure Timer1Timer(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


//テスト用アイコン作成
procedure MakeTestIcon(ImageList: TImageList);
var
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.Height := 16;
    Bitmap.Width := 16;
    Bitmap.Canvas.Brush.Color := clRed;
    Bitmap.Canvas.FillRect(Rect(2,2,14,14));
    ImageList.Add(Bitmap,nil);
    Bitmap.Canvas.Brush.Color := clBlue;
    Bitmap.Canvas.FillRect(Rect(2,2,14,14));
    ImageList.Add(Bitmap,nil);
  finally
    Bitmap.Free;
  end;
end;

// 初期設定いろいろ
procedure TForm1.FormCreate(Sender: TObject);
const
  FileName = 'data.txt';
var
  Node: TTreeNode;
  Path: String;
begin
  // 展開用タイマー設定
  Timer1.Enabled := False;
  Timer1.Interval := 1500;

  // スクロール用タイマー設定
  Timer2.Enabled := False;
  Timer2.Interval := 50;

  // アイコンの作成
  MakeTestIcon(ImageList1);

  // TreeView1の設定
  TreeView1.Images := ImageList1;
  TreeView1.ReadOnly := True;
  TreeView1.DragMode := dmAutomatic;
  TreeView1.Height := 150;

  // データの読み込み
  Path := ExtractFilePath(Application.ExeName);
  TreeView1.LoadFromFile(Path + FileName);

  Node := TreeView1.Items.GetFirstNode;
  repeat
    Node.ImageIndex := 1;
    Node.StateIndex := 2;
    Node := Node.GetNext;
  until Node = nil;

  // スクロールの際、ごみが出るから(^^;
  TreeView1.ControlStyle := TreeView1.ControlStyle - [csDisplayDragImage];
end;

var
  ScrollBarCommand: Integer;

procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);

  function IsSelfOrMyChild(dtNode, slNode: TTreeNode): Boolean;
  begin
    Result := False;
    while (dtNode <> nil) do
    begin
      if (slNode = dtNode) then
      begin
        Result := True;
        Exit;
      end;
      dtNode := dtNode.Parent;
    end;
  end;

const
  Offset = 20;
var
  slNode, dtNode: TTreeNode;
begin
  slNode := TreeView1.Selected;
  dtNode := TreeView1.DropTarget;

  Accept := ((Sender as TTreeView) = (Source as TTreeView)) and
            (not IsSelfOrMyChild(dtNode, slNode));

  if not Accept then Exit;
  
  // ノードを展開させます。
  if (dtNode <> nil) then
  begin
    if Timer1.Enabled and (Timer1.Tag <> dtNode.AbsoluteIndex) then
      Timer1.Enabled := False;
    if (not dtNode.Expanded) and dtNode.HasChildren then
    begin
      Timer1.Tag := dtNode.AbsoluteIndex;
      Timer1.Enabled := True;
    end;
  end;

  // 表示されていない部分をスクロールさせます。
  if (Y < Offset) or (Y >= TreeView1.ClientHeight - Offset) then
    begin
      if (Y < Offset) then
        ScrollBarCommand := SB_LINEUP
      else
        ScrollBarCommand := SB_LINEDOWN;
      Timer2.Enabled := True;
    end
  else
    begin
      ScrollBarCommand := -1;
      Timer2.Enabled := False;
    end;
end;

procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  srcNode, dstNode: TTreeNode;
  HT: THitTests;
begin
  // Explorer等は、ファイル名でソートされていますが、
  // ソートではなく意図したノードの順番に移動させたい場合があります。
  // そのような時に私のアプリでは、
  // アイコンで挿入移動、文字列で子追加移動という仕様にしています。
  // これって結構便利だと思うんですけど、あまり採用されてないみたい。

  srcNode :=TreeView1.Selected;           // 選択ノード
  dstNode := TreeView1.DropTarget;        // ドロップターゲットノード

  HT := TreeView1.GetHitTestInfoAt(X, Y);
  if (htOnIcon in HT) then
    srcNode.MoveTo(dstNode, naInsert)     // アイコンを選択している場合
  else
    srcNode.MoveTo(dstNode, naAddChild);  // 項目を選択している場合
end;

procedure TForm1.TreeView1EndDrag(Sender, Target: TObject; X, Y: Integer);
begin
  ScrollBarCommand := -1;
  Timer1.Enabled := False;
  Timer2.Enabled := False;
end;

// 閉じているノードを展開させます。
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  TreeView1.Items[Timer1.Tag].Expanded := True;
  Timer1.Enabled := False;
end;

// スクロールさせます。
procedure TForm1.Timer2Timer(Sender: TObject);
begin
  if (ScrollBarCommand > -1) then
    SendMessage(TreeView1.Handle, WM_VSCROLL, ScrollBarCommand, 0);
end;

end.

プログラムと同じフォルダに、下記の内容を書き込んだテキストファイルを作成して下さい。
ファイル名は、data.txtです。
node-01
	node-02
	node-03
		node-04
		node-05
node-06
	node-07
		node-08
node-09
	node-10
node-11
node-12
node-13
node-14
	node-15
node-16
node-17
node-18
node-19
node-20
node-21
	node-22
node-23

|

☆TreeViewでのテキスト選択状態の取得

TreeViewのテキスト編集で文字列が選択状態かどうかを判断します。
(ツールバーの有効/無効の判定に使っています。)
var
  StartPos, EndPos: Word;
begin
  if TreeView1.IsEditing then
    begin
      StartPos := LOWORD(SendMessage(
            TreeView_GetEditControl(TreeView1.Handle), EM_GETSEL, 0, 0));
      EndPos   := HIWORD(SendMessage(
            TreeView_GetEditControl(TreeView1.Handle), EM_GETSEL, 0, 0));
      acCut.Enabled := (StartPos <> EndPos);
    end
  else
    acCut.Enabled := (TreeView1.Selected <> nil) and
             (TreeView1.Selected.Level <> 0);
end;
※基本的なことですが、思い出すまでに随分時間がかかりました(^^;

|

☆TreeViewのDrag&Drop

[Windowsフォームアプリケーション]
HELPや[HOW TO] Visual C# .NET アプリケーションにツリー ビューのドラッグ アンド ドロップ機能を追加する方法を参考にTreeViewのDrag&Dropを試してみました。HELPでは、ListBoxでの参考例がありますが、Clickで移動、CTRL+Clickでコピーという動作を実装しています。

フォームにTreeView1を配置して、下記のプログラムを記述します。
procedure TWinForm1.TWinForm1_Load(sender: System.Object;
 e: System.EventArgs);
begin
 TreeView1.AllowDrop := True;

 TreeView1.Nodes.Add('Item1');
 TreeView1.Nodes.Add('Item2');
 TreeView1.Nodes.Add('Item3');
end;

procedure TWinForm1.TreeView1_ItemDrag(sender: System.Object;
 e: System.Windows.Forms.ItemDragEventArgs);
var
 DDE : DragDropEffects;
begin
 //ドラッグ開始
 DDE := TreeView1.DoDragDrop(e.Item, DragDropEffects.All);
 //動作の結果表示
 if ((DDE and DragDropEffects.Move) = DragDropEffects.Move) then
  Self.Text := '移動しました。'
 else if ((DDE and DragDropEffects.Copy) = DragDropEffects.Copy) then
  Self.Text := 'コピーしました。'
 else
  Self.Text := '処理しませんでした。';
end;

procedure TWinForm1.TreeView1_DragOver(sender: System.Object;
 e: System.Windows.Forms.DragEventArgs);

 function IsChild(Item1, Item2: TreeNode): Boolean;
 begin
  if (Item2.Parent = Item1)  then
   Result := True
  else if (Item2.Parent <> nil) then
   Result :=  IsChild(Item1, Item2.Parent)
  else
   Result := False;
 end;

var
 Target, Source: TreeNode;
begin
 //データ形式のチェック
 if not e.Data.GetDataPresent(TypeOf(TreeNode)) then
 begin
  e.Effect := DragDropEffects.None;
  Exit;
 end;

 //ドロップできるかどうか
 Target := TreeView1.GetNodeAt(
       TreeView1.PointToClient(Point.Create(e.X, e.Y)));
 Source := TreeNode(e.Data.GetData(TypeOf(TreeNode)));

 if ((Target <> nil) and (Target <> Source) and 
   (not IsChild(Source, Target))) then begin
  TreeView1.SelectedNode := Target;
  if ((e.KeyState and 8) = 8) and 
   ((e.AllowedEffect and DragDropEffects.Copy) = DragDropEffects.Copy) then
   e.Effect := DragDropEffects.Copy //CTRL + Left Click
  else if ((e.AllowedEffect and DragDropEffects.Move) = DragDropEffects.Move) then
   e.Effect := DragDropEffects.Move
  else
   e.Effect := DragDropEffects.None;
 end else
  e.Effect := DragDropEffects.None;
end;

procedure TWinForm1.TreeView1_DragDrop(sender: System.Object;
 e: System.Windows.Forms.DragEventArgs);
var
 Target, Source, NewNode: TreeNode;
begin
 if (e.Effect = DragDropEffects.None) then Exit;

 Source := TreeNode(e.Data.GetData(TypeOf(TreeNode)));
 Target := TreeView1.GetNodeAt(TreeView1.PointToClient(Point.Create(e.X, e.Y)));

 NewNode := TreeNode(Source.Clone);
 Target.Nodes.Add(NewNode);
 TreeView1.SelectedNode :=NewNode;

 if (e.Effect = DragDropEffects.Move) then
  Source.Remove;
end;

|

☆TreeViewを使ってみる。

[Windowsフォームアプリケーション]
<TreeNodeの追加と削除>
TreeViewへのTreeNodeの追加と削除を試してみました。なんかごちゃごちゃしていますが、 下記のプログラムで問題なく動作しているみたいです。
//選択ノードの兄弟を追加する。
procedure TWinForm.Button1_Click(sender: System.Object;
 e: System.EventArgs);
begin
 if (TreeView1.SelectedNode = nil) or
  (TreeView1.SelectedNode.Parent = nil) then
  TreeView1.Nodes.Add('Sibling')
 else
  TreeView1.SelectedNode.Parent.Nodes.Add('Sibling');
end;

//選択ノードの子を追加する。
procedure TWinForm.Button2_Click(sender: System.Object;
 e: System.EventArgs);
begin
 if (TreeView1.SelectedNode <> nil) then
  TreeView1.SelectedNode.Nodes.Add('Child');
end;

//選択ノード位置に挿入する。
procedure TWinForm.Button3_Click(sender: System.Object;
 e: System.EventArgs);
begin
 if (TreeView1.SelectedNode <> nil) then
 begin
  if (TreeView1.SelectedNode.Parent = nil) then
   TreeView1.Nodes.Insert
     (TreeView1.SelectedNode.Index, TreeNode.Create('Insert'))
  else
   TreeView1.SelectedNode.Parent.Nodes.Insert
     (TreeView1.SelectedNode.Index , TreeNode.Create('Insert'));
 end;
end;

//選択ノードを削除する。
procedure TWinForm.Button4_Click(sender: System.Object;
 e: System.EventArgs);
begin
 if (TreeView1.SelectedNode <> nil) then
  TreeView1.SelectedNode.Remove;
end;
<TreeNodeの取得>
TreeNodeの取得には下記のプロパティが用意されています。

FirstNode  ツリー ノード コレクション内の最初の子ツリー ノードを取得します。
LastNode  最後の子ツリー ノードを取得します。
NextNode  次のレベルにある兄弟ツリー ノードを取得します。
PrevNode  前のレベルにある兄弟ツリー ノードを取得します。
NextVisibleNode 次のレベルにある、表示されているツリー ノードを取得します。
PrevVisibleNode 前のレベルにある、表示されているツリー ノードを取得します。

<気になること>
私はTreeViewを使って階層構造のファイルの読み書きする時、Delphi7までは、TTreeNodeのLevelというプロパティを多用してきましたが、それが見当たりません。 その代わりになるかどうかわかりませんが、FullPathというプロパティがあります。 今後検討していきたいと思います。

|