2012年7月4日 星期三

在 WinForm 移動及縮放圖片(Image Viewer)


此篇目的很清楚~就是希望在 WinForm 中顯示圖片,並能放大及顯示圖片
首先先在 Form 中拉進 TrackBar(縮放圖片) 及 PictureBox(圖片容器)
透過 TackBar 的 Scroll 事件來變更圖片縮放比例
透過 PictureBox 的 MouseDown 事件來啟動移動狀態,並記錄位移的初始點
再透過 MouseMove 事件來計算位移量,並改變 PictureBox 的座標
透過 MouseUp 事件來結束移動狀態

範例如下:
##CONTINUE##

















Imports System.Drawing
Imports System.Windows.Forms

Public Class frmImageViewer
    Private _ImagePath As String
    Private _ImageWidth As Integer
    Private _ImageHeight As Integer

    Private _StarPoint As Point = Point.Empty
    Private _ViewPoint As Point = Point.Empty
    Private bMoveImage As Boolean = False

    Public Sub New(sImagePath As String)
        InitializeComponent()
        _ImagePath = sImagePath
        _ImageWidth = picImage.Width
        _ImageHeight = picImage.Height
    End Sub

    Private Sub frmImageViewer_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        picImage.ImageLocation = _ImagePath
    End Sub



    Private Sub TrackBar1_Scroll(sender As System.Object, e As System.EventArgs) Handles TrackBar1.Scroll
        picImage.Width = _ImageWidth * (TrackBar1.Value / 100)
        picImage.Height = _ImageHeight * (TrackBar1.Value / 100)
    End Sub

    Private Sub picImage_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles picImage.MouseDown
        bMoveImage = True
        Cursor = Cursors.Hand
        _StarPoint = e.Location
    End Sub

    Private Sub picImage_MouseMove(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles picImage.MouseMove
        If bMoveImage Then
            Dim _MovePoint As New Point(e.X - _StarPoint.X, e.Y - _StarPoint.Y)
            _ViewPoint.X = _MovePoint.X + _ViewPoint.X
            _ViewPoint.Y = _MovePoint.Y + _ViewPoint.Y
            picImage.Location = New Point(_ViewPoint.X, _ViewPoint.Y)
        End If
    End Sub

    Private Sub picImage_MouseUp(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles picImage.MouseUp
        bMoveImage = False
    End Sub
End Class



2012年6月29日 星期五

在 WinForm 的 DataGridView 中使用 DateTimePicker


在 WinForm 的 DataGridView 想要使用日期格式
但是在加入資料行時卻沒有相關型別可以使用
只好用 DataGridViewTextBoxColumn 來湊合著用
怕使用者若亂輸入非日期格式只好多寫點程式來檢查
難道沒有更好的方式?

答案就是自訂一個型別為 DateTimePicker 的 DataGridViewColumn
如下圖,是否跟你 DataGridView 所能增加控制項的數量不一樣? ##CONTINUE##

其做法如下
1. 繼承 DateTimePicker 並實作 IDataGridViewEditingControl
2. 繼承 DataGridViewTextBoxCell 來自訂 DataGridView 的 Cell,並把自訂的 DateTimePicker 放進 Cell
3. 繼承 DataGridViewColumn 來自訂 DataGridView 的 Column,並把自訂的 Cell 放進 Column

Public Class DataGridViewCalendarEditingControl
    Inherits DateTimePicker
    Implements IDataGridViewEditingControl

    Private dataGridViewControl As DataGridView
    Private valueIsChanged As Boolean = False
    Private rowIndexNum As Integer

    Public Sub New()
        Me.Format = DateTimePickerFormat.Short
    End Sub

    Public Property EditingControlFormattedValue() As Object _
        Implements IDataGridViewEditingControl.EditingControlFormattedValue
        Get
            Return Me.Value.ToShortDateString()
        End Get
        Set(ByVal value As Object)
            If TypeOf value Is [String] Then
                Me.Value = DateTime.Parse(CStr(value))
            End If
        End Set
    End Property

    Public Function GetEditingControlFormattedValue(ByVal context _
        As DataGridViewDataErrorContexts) As Object _
        Implements IDataGridViewEditingControl.GetEditingControlFormattedValue
        Return Me.Value.ToShortDateString()
    End Function

    Public Sub ApplyCellStyleToEditingControl(ByVal dataGridViewCellStyle As  _
        DataGridViewCellStyle) _
        Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl
        Me.Font = dataGridViewCellStyle.Font
        Me.CalendarForeColor = dataGridViewCellStyle.ForeColor
        Me.CalendarMonthBackground = dataGridViewCellStyle.BackColor
    End Sub

    Public Property EditingControlRowIndex() As Integer _
        Implements IDataGridViewEditingControl.EditingControlRowIndex
        Get
            Return rowIndexNum
        End Get
        Set(ByVal value As Integer)
            rowIndexNum = value
        End Set
    End Property

    Public Function EditingControlWantsInputKey(ByVal key As Keys, _
        ByVal dataGridViewWantsInputKey As Boolean) As Boolean _
        Implements IDataGridViewEditingControl.EditingControlWantsInputKey

        Select Case key And Keys.KeyCode
            Case Keys.Left, Keys.Up, Keys.Down, Keys.Right, _
                Keys.Home, Keys.End, Keys.PageDown, Keys.PageUp
                Return True
            Case Else
                Return False
        End Select
    End Function

    Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) _
        Implements IDataGridViewEditingControl.PrepareEditingControlForEdit
    End Sub

    Public ReadOnly Property RepositionEditingControlOnValueChange() _
        As Boolean Implements _
        IDataGridViewEditingControl.RepositionEditingControlOnValueChange
        Get
            Return False
        End Get
    End Property

    Public Property EditingControlDataGridView() As DataGridView _
        Implements IDataGridViewEditingControl.EditingControlDataGridView
        Get
            Return dataGridViewControl
        End Get
        Set(ByVal value As DataGridView)
            dataGridViewControl = value
        End Set
    End Property

    Public Property EditingControlValueChanged() As Boolean _
        Implements IDataGridViewEditingControl.EditingControlValueChanged
        Get
            Return valueIsChanged
        End Get
        Set(ByVal value As Boolean)
            valueIsChanged = value
        End Set
    End Property

    Public ReadOnly Property EditingControlCursor() As Cursor _
        Implements IDataGridViewEditingControl.EditingPanelCursor
        Get
            Return MyBase.Cursor
        End Get
    End Property

    Protected Overrides Sub OnValueChanged(ByVal eventargs As EventArgs)
        valueIsChanged = True
        Me.EditingControlDataGridView.NotifyCurrentCellDirty(True)
        MyBase.OnValueChanged(eventargs)
    End Sub
End Class

Public Class DataGridViewCalendarCell
    Inherits DataGridViewTextBoxCell

    Public Sub New()
        Me.Style.Format = "d"
    End Sub

    Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, _
        ByVal initialFormattedValue As Object, _
        ByVal dataGridViewCellStyle As DataGridViewCellStyle)
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, _
            dataGridViewCellStyle)

        Dim ctl As DataGridViewCalendarEditingControl = _
            CType(DataGridView.EditingControl, DataGridViewCalendarEditingControl)
        If Not Me.Value Is Nothing Then
            ctl.Value = CType(Me.Value, DateTime)
        End If
    End Sub

    Public Overrides ReadOnly Property EditType() As Type
        Get
            Return GetType(DataGridViewCalendarEditingControl)
        End Get
    End Property
End Class

Public Class DataGridViewCalendarColumn
    Inherits DataGridViewColumn
    Public Sub New()
        MyBase.New(New DataGridViewCalendarCell())
    End Sub

    Public Overrides Property CellTemplate() As DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(ByVal value As DataGridViewCell)
            If Not (value Is Nothing) AndAlso _
                Not value.GetType().IsAssignableFrom(GetType(DataGridViewCalendarCell)) _
                Then
                Throw New InvalidCastException("Must be a CalendarCell")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property
End Class

應用的情況如下圖

WinForm 中使用 RadioButtonList


RadioButtonList 在 ASP.NET 中是再平常不過的控制項
但在 WinForm 中卻怎樣也找不著
若要在 WinForm 使用一個以上的 RadioButton
最常見的做法就是用一個 GroupBox 或 Panel 把多個 RadioButton 包起來
要取值時再跑迴圈去遍歷

但...若不確定有多少的 RadioButton 時...要怎麼預先拉 RadioButton 的數量?
可否從資料庫撈資料以 DataBind 的方式來產生 RadioButton 的數量
這時又需要撰寫 User Control

做法如下
##CONTINUE##

Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Drawing
Imports System.Data
Imports System.Text
Imports System.Windows.Forms
Imports System.Collections
Imports tw.org.mirdc

Partial Public Class RadioButtonList
    Inherits UserControl

#Region "Variable"
    Private _dataSource As [Object]
    Private _mappings As ButtonValueMapping()
    Private _internalDataSource As [Object]
    Private _displayMember As String
    Private _valueMember As String
    Private _selectedValue As String
    Private _selectedName As String
#End Region

#Region "ctor."
    Public Sub New()
        InitializeComponent()
    End Sub
#End Region

#Region "prop"
    Public Property DataSource() As System.Object
        Get
            Return _dataSource
        End Get
        Set(ByVal value As System.Object)
            _dataSource = value
        End Set
    End Property

    Public Property DisplayMember() As String
        Get
            Return _displayMember
        End Get
        Set(ByVal value As String)
            _displayMember = value
        End Set
    End Property

    Public Property ValueMember() As String
        Get
            Return _valueMember
        End Get

        Set(ByVal value As String)
            _valueMember = value
        End Set

    End Property

    Public Property SelectedValue() As String
        Get
            Return _selectedValue
        End Get

        Set(ByVal value As String)
            If value IsNot Nothing Then
                _selectedValue = value
                SetValue(value)
            End If
        End Set
    End Property

    Public Property SelectedName() As String
        Get
            Return _selectedName
        End Get
        Set(ByVal value As String)
            If value IsNot Nothing Then
                _selectedName = value
                SetValue(value)
            End If
        End Set
    End Property
#End Region

#Region "event"
    Public Delegate Sub RadioSelectedHandler(ByVal sender As Object, ByVal e As SelectedEventArgs)
    Public Event RadioItemSeleted As RadioSelectedHandler
#End Region

#Region "public method"
    Public Sub DataBind()
        If _dataSource Is Nothing Then
            Throw New NullReferenceException("Null reference in Property: DataSource ")
        End If
        PrepareData()
        DrawControl()
    End Sub
#End Region

#Region "internal function"
    Private Sub DrawControl()
        Panel1.Controls.Clear()
        Dim count As Integer = _mappings.Length
        Dim height As Integer = 0
        Dim width As Integer = 0
        Dim x_aris As Integer = 0
        Dim y_aris As Integer = 0
        For i As Integer = 0 To count - 1
            Dim radio As New RadioButton()
            radio.Name = i.ToString()
            radio.Text = _mappings(i).Text
            radio.AutoSize = True
            Panel1.Controls.Add(radio)
            height += radio.Height
            radio.Location = New Point(x_aris + width, y_aris)
            width += radio.Width
            AddHandler radio.Click, New EventHandler(AddressOf radio_Click)
        Next
        If count > 0 Then
            Panel1.Height = DirectCast(Panel1.Controls(_mappings(0).Index.ToString()), RadioButton).Height
            Me.Height = Panel1.Height
            Me.Panel1.Width = width
            Me.Width = width
        End If
    End Sub  

    Private Sub PrepareData()
        _internalDataSource = Nothing
        If TypeOf _dataSource Is DataTable Then
            _internalDataSource = DirectCast(_dataSource, DataTable).DefaultView
        End If
        If TypeOf _dataSource Is DataView Then
            _internalDataSource = _dataSource
        ElseIf TypeOf _dataSource Is List(Of mirdc.Entity.LookupTable) Then
            _internalDataSource = _dataSource
        End If
        If _internalDataSource Is Nothing Then
            Throw New InvalidCastException("The data source is not a desinged type.")
        End If
        If TypeOf _internalDataSource Is DataView Then
            Dim radioCount As Integer = DirectCast(_internalDataSource, DataView).Count
            _mappings = New ButtonValueMapping(radioCount - 1) {}
            Try
                For i As Integer = 0 To radioCount - 1
                    _mappings(i).Index = i
                    _mappings(i).Text = DirectCast(_internalDataSource, DataView)(i)(_displayMember).ToString()
                    If _valueMember Is Nothing OrElse _valueMember = String.Empty Then
                        _mappings(i).Value = i.ToString()
                    Else
                        _mappings(i).Value = DirectCast(_internalDataSource, DataView)(i)(_valueMember).ToString()
                    End If
                Next
            Catch e As Exception
                Throw e
            End Try
        End If
    End Sub

    Private Sub radio_Click(ByVal sender As Object, ByVal e As EventArgs)
        _selectedValue = _mappings(Integer.Parse(DirectCast(sender, RadioButton).Name)).Value
        Dim se As New SelectedEventArgs()
        se.Value = _mappings(Integer.Parse(DirectCast(sender, RadioButton).Name)).Value
        RaiseEvent RadioItemSeleted(Me, se)
    End Sub

    Private Sub SetValue(ByVal value As String)
        If _mappings Is Nothing Then
            Throw New NullReferenceException("Data has not bound to the control")
        End If
        For Each map As ButtonValueMapping In _mappings
            If map.Value = value Then
                DirectCast(Panel1.Controls(map.Index.ToString()), RadioButton).Checked = True
            End If
        Next
    End Sub
#End Region

    Friend Structure ButtonValueMapping
        Public Index As Integer
        Public Value As String
        Public Text As String
    End Structure
    Public Class SelectedEventArgs
        Inherits EventArgs
        Public Value As String
    End Class
End Class

編譯完後就會在工具箱出現了,這時應該知道該怎麼用了吧? 拖拉 & 複製貼上是 programmer 的強項 CheckBoxList亦可仿製

2012年6月28日 星期四

在 WinForm 的 DataGridView 中將Button設置為 Disable


  • 使用情境

有許多情況我們必須根本不同條件來控制是否啟用每一行 DataGridView 的按鈕,而不是將整列按鈕隱藏
例如依權限開放按鈕給使用者,若無權限則將該行按鈕關閉或隱藏


這件事在 ASP.NET 中的實作是輕而易舉,但在 WinForm 中卻找不到 DataGridViewButtonCell 有  Enabled 的屬性來控制按鈕的啟用狀態
##CONTINUE##

  • 實作方法

故為達此目的,必須透過繼承 DataGridViewButtonCell 來建立 User Control的方式來擴充功能
程式碼如下

Public Class DataGridViewDisableButtonCell
    Inherits DataGridViewButtonCell
    Private enabledValue As Boolean

    Public Property Enabled() As Boolean
        Get
            Return enabledValue
        End Get
        Set(value As Boolean)
            enabledValue = value
        End Set
    End Property

    Public Overrides Function Clone() As Object
        Dim cell As DataGridViewDisableButtonCell = DirectCast(MyBase.Clone(), DataGridViewDisableButtonCell)
        cell.Enabled = Me.Enabled
        Return cell
    End Function

    Public Sub New()
        Me.enabledValue = True
    End Sub

    Protected Overrides Sub Paint(graphics As Graphics, clipBounds As Rectangle, cellBounds As Rectangle, rowIndex As Integer, elementState As DataGridViewElementStates, value As Object, _
     formattedValue As Object, errorText As String, cellStyle As DataGridViewCellStyle, advancedBorderStyle As DataGridViewAdvancedBorderStyle, paintParts As DataGridViewPaintParts)

        If Not Me.enabledValue Then
            If (paintParts And DataGridViewPaintParts.Background) = DataGridViewPaintParts.Background Then
                Dim cellBackground As New SolidBrush(cellStyle.BackColor)
                graphics.FillRectangle(cellBackground, cellBounds)
                cellBackground.Dispose()
            End If

            If (paintParts And DataGridViewPaintParts.Border) = DataGridViewPaintParts.Border Then
                PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle)
            End If

            Dim buttonArea As Rectangle = cellBounds
            Dim buttonAdjustment As Rectangle = Me.BorderWidths(advancedBorderStyle)
            buttonArea.X += buttonAdjustment.X
            buttonArea.Y += buttonAdjustment.Y
            buttonArea.Height -= buttonAdjustment.Height
            buttonArea.Width -= buttonAdjustment.Width
            ButtonRenderer.DrawButton(graphics, buttonArea, VisualStyles.PushButtonState.Disabled)

            If TypeOf Me.FormattedValue Is [String] Then
                TextRenderer.DrawText(graphics, DirectCast(Me.FormattedValue, String), Me.DataGridView.Font, buttonArea, SystemColors.GrayText)
            End If
        Else
            MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, _
             formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)
        End If
    End Sub
End Class

重建專案後,在新增 DataGridView 的欄位時就可看到剛剛建立的 DataGridViewDisableButtonCell


控制 Button Enabled 狀態通常是在 DataBind 之後,故攔截 DataBindingComplete 事件,將判斷實作於此

例如
        For Each oRow As DataGridViewRow In gvProjectFlow.Rows

            '依權限開啟 button
            Dim iFlowID As Integer = Integer.Parse(oRow.Cells("FlowID").Value)
            Dim oProjectFlowEntity As Entity.ProjectFlow = oProjectFlowBll.GetEntity(sProjectNumber, iFlowID)
            Dim oButtonCell As DataGridViewDisableButtonCell = CType(oRow.Cells("Excute"), DataGridViewDisableButtonCell)

            If oProjectFlowEntity.AssigneeUserName = UserSession.UserName And oProjectFlowEntity.State = Common.ProjectStateEnum.Incomplete Then
                oButtonCell.Enabled = True
            Else
                oButtonCell.Enabled = False
            End If
        Next





2009年8月1日 星期六

PDM/PLM (SmarTeam) 二次開發 - Lesson Six - 進階功能 - 以資料庫管理專案預建的資料夾結構

此篇是上一篇「建立專案時預建資料夾」的進階版

上一篇是將要欲新增的資料夾名稱及結構寫在程式裡,那其實就跟 SDE 沒啥兩樣了,要修改就很麻煩,除了改程式外還要一一更新客戶端

故此篇要將資料夾的名稱及結構存於資料庫,維護時只要從資料庫下手,改完資料庫後就馬上生效,不用一一更新客戶端

首先先建立一存放資料夾結構的 Table「FolderStructureTest」##CONTINUE##

CREATE TABLE [dbo].[FolderStructureTest](
 [ID] [int] IDENTITY(1,1) NOT NULL,
 [ParentID] [int] NULL,
 [FolderName] [nvarchar](50) COLLATE Chinese_Taiwan_Stroke_CI_AS NULL
) ON [PRIMARY]

建立的資料內容如下圖,如果 ParentID 為 null 的表示直接link 於 Project,若有 ID 的表示成為 Folder 子階


再來只要修改上一篇範例中建立資料夾的 function (CreateProjectFoldersTest),改由讀取資料庫即可,其他 function 不改

Function CreateProjectFoldersTest(ByVal SmSession As SmApplic.SmSession, ByVal Operation As SmApplic.ISmOperation, ByVal FirstPar As SmRecList.SmRecordList, ByVal SecondPar As SmRecList.SmRecordList, ByVal ThirdPar As SmRecList.SmRecordList) As String

 Dim Project As ISmObject
 Dim smObject As ISmObject
 Dim sRetObjectID As String
 Project = SmSession.ObjectStore.ObjectFromData(FirstPar.GetRecord(0), False)

 GetConnectionString()
 Dim dbh As New DbHelperSQL(sSH_ConnectionString)
 Dim sSql As String = "select ID, ParentID, FolderName from FolderStructureTest"

 Dim dt As DataTable
 dt = dbh.Query(sSql).Tables(0)

 Dim retObject As ISmObject
 Dim tmpObject As ISmObject
 Dim sProjectID As String = Project.Value("TDMX_ID").ToString()

 Dim i As Integer = 0
 For i = 0 To dt.Rows.Count - 1
  If dt.Rows(i)("ParentID").ToString = "" Then
   retObject = CreateProjectSubfolderTest(SmSession, dt.Rows(i)("FolderName").ToString, Project)
   Dim dv As New DataView(dt)
   dv.RowFilter = "ParentID=" + dt.Rows(i)("ID").ToString
   If dv.Count > 0 Then
    For Each row As DataRowView In dv
      CreateSubfolderTest(SmSession, row("FolderName").ToString(), retObject)
    Next
   End If
  End If
 Next

 Return sRetObjectID
End Function

執行後結果如下圖