<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Java桌面技術

    Java Desktop Technology

    常用鏈接

    統計

    友情連接

    最新評論

    SWT自定義組件之Slider

       曾經介紹過用SWT實現MSN風格的下拉框,SWT雖然沒有Swing那么強大,尤其是在打造專業外觀上,不支持L&F,但是通過自定義組件,同樣可以達到用戶要求。下面就向大家介紹本人實現的一個具備專業外觀的Slider控件。
        首先來參考一下組件的實際運行效果,并和SWT原生組件進行一下對比。 

                                                           

    可以看出,經過自定義的組件在外觀上要比SWT直接調用本地組件顯得更加專業。當用戶托拽滑動塊時,還會出現一個虛擬的滑動塊用來標識將要移動到的位置。演示就到此為止,下面詳細介紹這個很Cool的組件是如何通過SWT實現的。

        基本設計思想:與其他自定義組件一樣,是通過繼承org.eclipse.swt.widgets.Composite來實現,定義該類為Slider,另外滑動塊(thumb)也是Composite,并放在Slider之上,當鼠標移動thumb時,調用setBounds方法定位在Slider在父組件(Slider)上的位置,從而達到拖拽thumb的目的。此外通過實現PaintListener接口進行自定義繪制,繪制的對象包括組件邊框、被填充的格子、未被填充的格子、虛擬滑塊。

        接觸過GUI編程的程序員都應該知道像Scroll、Slider、ProgressBar這樣的控件都有setMaxValue、setMinValue、setValue這樣的方法,除了鼠標拖拽thumb來改變當前數值外,可直接調用setValue來設置當前值。此外這些控件還有水平(Horizontal)、垂直(Vertical)兩種布局,對于事件處理一般都要有一個從java.util.EventObject繼承而來的事件類,還要編寫事件監聽器(Listener)接口,因此在開始編寫Slider控件之前先定義3個類,代碼都不是很長,如果你熟悉AWT、Swing的事件處理機制,相信你能輕松跳過。


    public enum SliderOrientation {
     HORIZONTAL, VERTICAL;
    }

    public class SliderEvent extends EventObject {

     private int value;

     public SliderEvent(Object source, int value) {
      super(source);
      this.value = value;
     }

     public int getValue() {
      return value;
     }
    }

    public interface SliderListener {

     public void valueChanged(SliderEvent event);
    }

        接下來著重介紹Slider。首先是繼承Composite并實現ControlListener、PaintListener、MouseListener,、MouseMoveListener,、MouseTrackListener,然后自動生成接口方法代碼,通過Eclipse可以輕松實現,需要注意的是MouseListener,有java.awt.event.MouseListener和org.eclipse.swt.events.MouseListener兩種,不要混淆,否則錯誤很難找到。然后是要采集一些數據信息,分別是:邊框顏色、已有數據部分的填充顏色(上圖中組件的綠色部分)、未達到數據部分的填充顏色(上圖中組件的白色部分)、被禁用時的填充顏色、水平滑塊的圖標(正常、托拽中兩種)、垂直滑塊圖標(正常、托拽中兩種)、水平、垂直虛擬滑塊圖標。以上這些數據對應的常量聲明如下:

     private final Color BORDER_COLOR = new Color(Display.getCurrent(), 180, 188, 203);

     private final Color FILL_COLOR = new Color(Display.getCurrent(), 147, 217, 72);

     private final Color BLANK_COLOR = new Color(Display.getCurrent(), 254, 254, 254);

     private final Color DISABLE_COLOR = new Color(Display.getCurrent(), 192, 192, 192);

     private final Image THUMB_ICON_V = new Image(Display.getDefault(), "slider_up_v.png");

     private final Image THUMB_OVER_ICON_V = new Image(Display.getDefault(), "slider_over_v.png");

     private final Image THUMB_ICON_H = new Image(Display.getDefault(), "slider_up_h.png");

     private final Image THUMB_OVER_ICON_H = new Image(Display.getDefault(), "slider_over_h.png");

     private final Image TEMP_H = new Image(Display.getDefault(), "temp_h.png");

     private final Image TEMP_V = new Image(Display.getDefault(), "temp_v.png");

     除了這些常量,還應該聲明默認最大值的常量,private final int DEFAULT_MAX_VALUE = 100;
    接下來定義當前數值和最大值,
    private int value;
    private int maxValue = DEFAULT_MAX_VALUE;
    并生成以上兩個成員屬性的get方法
    然后定義滑動塊和布局
    private SliderOrientation orientation;
    private Composite thumb;
    要處理數值變化,需要實現一組監聽器,添加如下代碼
    private List<SliderListener> listeners = new ArrayList<SliderListener>();
    public void addSliderListener(SliderListener sliderListener) {
     listeners.add(sliderListener);
    }
    public void removeSliderListener(SliderListener sliderListener) {
     listeners.remove(sliderListener);
    }
    接下來定義2個輔助方法,實現value<->pelsLength轉換。其中value是當前的數值,由具體業務來決定,下文中稱其業務值。例如一個音量控制器,音量范圍在0~500,那么從業務上來講可以將數值設置在0~500之間的任何數,而pelsLength則由控件的長/高度來決定,單位是像素。但是value與pelsLength之間存在著一個比例關系式:value/maxValue=pelsLength/控件長度或高度。這樣不難得出兩個函數的定義。
    private int valueToPels(int value) {
      float widgetLength = (orientation == SliderOrientation.HORIZONTAL) ? getBounds().width
        : getBounds().height;
      return (int) (widgetLength * (float) value / (float) maxValue);
     }

     private int pelsToValue(int pels) {
      float widgetLength = (orientation == SliderOrientation.HORIZONTAL) ? getBounds().width
        : getBounds().height;
      return (int) ((float) pels * (float) maxValue / (float) widgetLength);
     }
    最后定義構造器。代碼如下
    public Slider(Composite parent, SliderOrientation orientation) {
      super(parent, SWT.FLAT);
      this.orientation = orientation;
      thumb = new Composite(this, SWT.FLAT);
      thumb
        .setBackgroundImage(orientation == SliderOrientation.VERTICAL ? THUMB_ICON_V
          : THUMB_ICON_H);
      addControlListener(this);
      addPaintListener(this);
      thumb.addMouseListener(this);
      thumb.addMouseMoveListener(this);
      thumb.addMouseTrackListener(this);
     }
    在構造器中,注入布局對象,然后在控件上創建滑動塊組件thumb,并添加鼠標處理等。
    到此為止,基本的成員和方法的定義完畢,下面循序漸進討論如何實現這一Slider。

    一、繪制邊框
    由于是繪制操作,所以一切繪制代碼均在paintControl方法內實現,先將如下代碼拷貝到paintControl內
    int w = getBounds().width;
    int h = getBounds().height;
    int fillLength = valueToPels(getValue());
    GC gc = e.gc;
    switch (orientation) {
     case HORIZONTAL:
      break;
     case VERTICAL:
      break;
    }
    分析如下
    首先獲取控件的長度與高度,因為接下來的繪制要經常用到這兩個變量。
    “int fillLength = valueToPels(getValue());”這一行代碼稍后作解釋,然后是獲得繪制上下文對象,下一步是根據布局不同采用不同的處理,除了paintControl函數,在其他很多地方都對布局進行判斷,但是簡單起見,只對水平布局進行介紹,垂直部分參考完整程序。
    接下來的繪制操作均在case HORIZONTAL中進行,首先將顏色設置為邊框的顏色
    gc.setForeground(BORDER_COLOR);然后繪制一個矩形gc.drawRectangle(0, 2, w - 1, h - 5);
    關于為什么要偏移2像素、長度為什么減1、高度為什么減5,請參考有關繪圖的基本知識,上一篇也有簡單的介紹。

    現在,你就可以編寫測試程序來驗證結果了,看看邊框是否與示例的效果一樣。

    二、托拽thumb的實現
    桌面GUI編程領域技術深淺的度量衡通常有4項指標:皮膚(外觀,swing組件體系稱其L&F)、繪圖、自定義組件布局(Layout)、自定義組件。而托拽是實現自定義組件和繪圖不可或缺的技術,也是難點之一,因此掌握的深淺是衡量桌面編程水平的標志。
    雖然作為難點,但是也有章可循,其基本實現簡單到只監聽鼠標事件這么簡單,基本流程是:當鼠標在thumb上按下時,記住這個位置,然后按住鼠標左鍵托拽,最后松開鼠標計算兩個位置之間的距離(位移),根據位移量移動thumb的位置并換算出等價的value增量(可能為負值)進行業務邏輯處理。下面通過代碼循序漸進完成。
    定義一個位置變量用來存儲鼠標單擊的位置,private Point controlPoint;然后實現public void mouseDown(MouseEvent e)和public void mouseUp(MouseEvent e)兩個方法。
    public void mouseDown(MouseEvent e) {
      controlPoint = new Point(e.x, e.y);
      thumb
        .setBackgroundImage(orientation == SliderOrientation.VERTICAL ? THUMB_OVER_ICON_V
          : THUMB_OVER_ICON_H);
     }

     public void mouseUp(MouseEvent e) {
      try {
       thumb
         .setBackgroundImage(orientation == SliderOrientation.VERTICAL ? THUMB_ICON_V
           : THUMB_ICON_H);
       countValue(e);
      } finally {
       controlPoint = null;
      }
     }
    “controlPoint = new Point(e.x, e.y);”這一行實現記住鼠標點的位置,注意,這個位置是相對滑塊thumb的,因為是thumb監聽的鼠標事件。接下來是設置滑動塊背景,不難理解當鼠標松開時,應該將背景恢復。然后進行非常重要的換算工作,通過countValue方法實現,最后務必要把鼠標位置清空,用try-finally是有必要這樣的。之所以要在方法結束的時候清空controlPoint,是因為在鼠標移動的時候需要對controlPoint進行更加復雜的計算。稍后講解mouseMove實現的時候再作解釋,接下來著重分析countValue方法。
    如前所述,countValue完成計算鼠標按下、松開的位移量,換算成與業務相關的數據(value)。
    private void countValue(MouseEvent e) {
      switch (orientation) {
      case HORIZONTAL:
       int movedX = e.x - controlPoint.x;
       setValue(getValue() + pelsToValue(movedX));
       break;
      case VERTICAL:
       ......
      }
     }
    “int movedX = e.x - controlPoint.x;”實現了位移量的計算,保存到movedX,調用pelsToValue方法將movedX轉換成業務值的增量,然后調用setValue重新賦值,注意pelsToValue得到的是增量,需要與原值(getValue()得到)疊加。接下來分析setValue方法。
    public void setValue(int value) {
      if (value < 0) {
       this.value = 0;
      } else if (value > getMaxValue()) {
       this.value = getMaxValue();
      } else {
       this.value = value;
      }
      try {
       moveThumb();
       redraw();
      } finally {
       for (SliderListener listener : listeners) {
        try {
         SliderEvent event = new SliderEvent(this, getValue());
         listener.valueChanged(event);
        } catch (Exception e) {
         e.printStackTrace();
        }
       }
      }
     }
    方法開始處的一系列if語句對value進行驗證后再賦值,然后是調用moveThumb實現滑塊移動、調用redraw對組件重新繪制,最后是處理具體的業務,可以看出處理業務是setValue方法的關鍵,所以要用try-finally,誰也不敢確保moveThumb、redraw不出問題。對于實現業務是遍歷監聽器列表然后執行每個監聽器的valueChanged方法,這種事件源-監聽器模型也是Java2以后的GUI事件實現模型。

     private void moveThumb() {
      Image icon = thumb.getBackgroundImage();
      int iconw = (icon != null) ? icon.getBounds().width : 0;
      int iconh = (icon != null) ? icon.getBounds().height : 0;
      switch (orientation) {
      case HORIZONTAL:
       int x = valueToPels(getValue()) - iconw / 2;
       if (x < 0) {
        x = 0;
       } else if (x > getBounds().width - iconw) {
        x = getBounds().width - iconw;
       }
       thumb.setBounds(x, 0, iconw, iconh);
       break;
      case VERTICAL:
       ......  }
     }
    不難理解,moveThumb的任務就是根據業務值value來將滑塊移動到正確的位置。
    以上代碼聲明滑塊的位置是“x”,通過轉換函數獲得,但是還要減去滑塊的一半,因為具體坐標應該落到滑動塊的中間,仔細想想不難得出。if-else是對x進行驗證,最后通過setBound來定位thumb。
    對于redraw,他的作用是觸發paintControl方法進行重繪,因為重繪出了邊框還要根據value繪制填充格子,而value已經在redraw方法調用前被賦了值,所以這時候應該進行重繪。
    現在你可以托拽thumb了,美中不足的是滑動塊不能隨時跟隨鼠標的軌跡移動,這個稍后會實現。

    三、填充格子
    現在的組件外觀只是繪制了邊框,現在進行格子填充。在講述邊框繪制的時候提到了一行代碼“int fillLength = valueToPels(getValue());”現在不難理解吧,就是將當前業務值value轉換成實際的長度。在繪制邊框之后,加入如下代碼:
    if (getEnabled()) {
        gc.setBackground(FILL_COLOR);
        for (int i = 2; i < w - 2; i += 4) {
         if (i > fillLength) {
          gc.setBackground(BLANK_COLOR);
         }
         gc.fillRectangle(i, 4, 3, h - 8);
        }
       } else {
        gc.setBackground(DISABLE_COLOR);
        gc.fillRectangle(1, 4, w - 1, h - 8);
       }
    首先判斷是否是enable,然后設置填充顏色FILL_COLOR,然后在for循環中執行正方形格子的填充,遞增量“i”從2開始是空開2像素間隔,同理i也不能超過w-2(對稱性),i+=4是相鄰兩個格子左邊坐標間距4像素,然后“gc.fillRectangle(i, 4, 3, h - 8);”這一行進行填充繪制正方形。留意,x坐標是i,y坐標是從4開始畫的,出于對稱高度也要“h-8”,長度之所以是“3”是保持相鄰兩個格子之間保持1像素的間隔(想想“i+=4”就不難得出答案)。此外還要對fillLength進行判斷以便決定顏色采用綠色還是白色以示區分。對于繪圖操作來說,千萬不要埋怨考慮細節過多,事實上,GUI編程過程中“坐標系”這個概念是需要經常被考慮的,試想如果上述代碼for循環中把“i+=4”,寫成“i+=5”,只是一個像素之差繪制效果差之千里,如果想知道筆者是如何得到這些坐標數據的,實話說,是靠多次調試得出的結果。
    現在你運行程序,應該能根據value進行格子填充了。到此為止,絕大多數的功能已經實現,但是人性化的界面設計應該在托拽時出現一個虛擬的滑動塊用來標識將要移動到的位置。好,繼續實現這一功能。
    定義一個int變量紀錄thumb的臨時位置,private int tempLocation;然后在paintControl添加如下紅色代碼
    case HORIZONTAL:
       gc.setForeground(BORDER_COLOR);
       gc.drawRectangle(0, 2, w - 1, h - 5);
       if (getEnabled()) {
        gc.setBackground(FILL_COLOR);
        for (int i = 2; i < w - 2; i += 4) {
         if (i > fillLength) {
          gc.setBackground(BLANK_COLOR);
         }
         gc.fillRectangle(i, 4, 3, h - 8);
        }
        if (controlPoint != null) {
         gc.drawImage(TEMP_H, tempLocation, 0);
        }
       } else {
        gc.setBackground(DISABLE_COLOR);
        gc.fillRectangle(1, 4, w - 1, h - 8);
       }
       break;
    很直觀,對于水平布局就是在(tempLocation,0)畫出“TEMP_H”圖標。外面的if很重要,還記得mouseDown方法中的語句“controlPoint = new Point(e.x, e.y);”和mouseUp方法中的語句“controlPoint = null;”嗎,當鼠標按下托拽開始時,為controlPoint賦值,當鼠標完成托拽松開時,將controlPoint置null,在這個托拽過程中controlPoint一直保持非null狀態,所以paintControl方法才將圖標畫出。如果現在就著急運行程序則會發現,鼠標托拽時候虛擬圖標總停留在最左邊(如果垂直布局停留在最上邊,因為并沒有為tempLocation賦值默認是0),不會跟隨鼠標移動。如果要實現真正的托拽那么必須在鼠標移動時反復執行paintControl,下面花大量的筆墨詳細地介紹mouseMove方法的實現,并簡單介紹繪圖操作的一些原理和流程。
    開門見山,直接將mouseMove函數的全部代碼列出。
    public void mouseMove(MouseEvent e) {
      if (controlPoint == null) {
       return;
      }
      int maxLength;
      int maxLocator;
      switch (orientation) {
      case HORIZONTAL:
       maxLength = valueToPels(getMaxValue());
       maxLocator = maxLength - TEMP_H.getBounds().width;
       int movedX = e.x - controlPoint.x;
       redraw(tempLocation, 0, TEMP_H.getBounds().width,
         TEMP_H.getBounds().height, false);
       tempLocation = valueToPels(getValue()) + movedX
         - TEMP_H.getBounds().width / 2;
       if (tempLocation < 0) {
        tempLocation = 0;
       } else if (tempLocation > maxLocator) {
        tempLocation = maxLocator;
       }
       break;
      case VERTICAL:
       ......
       break;
      }
     }
    最前面的if語句表明,只有鼠標按下時移動鼠標才算托拽,道理前面已經闡明了。maxLength代表最大值轉換得到的像素,maxLocator是虛擬圖標左端(上端)最大坐標,movedX代表托拽的位移量。最下面的if-else if目的很明了。整個mouseMove函數中
    redraw(tempLocation, 0, TEMP_H.getBounds().width,
         TEMP_H.getBounds().height, false);
       tempLocation = valueToPels(getValue()) + movedX
         - TEMP_H.getBounds().width / 2;
    是整個托拽操作最難懂也是技術含量最高的兩條語句。簡單起見暫時用下面的語句代替
    tempLocation = valueToPels(getValue()) + movedX
         - TEMP_H.getBounds().width / 2;
       if (tempLocation < 0) {
        tempLocation = 0;
       } else if (tempLocation > maxLocator) {
        tempLocation = maxLocator;
       }
       redraw();
    其中“tempLocation”的賦值語句不變,變化的是redraw函數的調用位置和參數。這樣的變化使得意思就不難理解了,首先確定tempLocation的值,等號右邊的計算結果也不難理解,然后調用redraw方法重畫組件。如果這時候你運行程序,托拽時確實虛擬光標會跟隨鼠標移動,但是也會發現組件閃爍得很厲害!具體程度取決于用戶計算機的性能,關于“組件重繪時閃爍”的問題是繪圖操作的一個常見問題,不僅僅是Java,任何支持繪圖的計算機語言都可以暴露這樣的問題,當年在大學用MFC、VB的編寫過畫圖板的人應該熟悉這類問題。
    在具體討論之前先簡單講述鼠標監聽器中的mouseMove操作
    一旦為GUI組件添加鼠標移動監聽器,當鼠標光標在組件上移動時便調用監聽器接口的mouseMove(MouseEvent e)方法,mouseMove調用的頻率與鼠標移動的快慢有關,移動越快mouseMove被調用的次數就越少,反之就越多。假設鼠標從組件上的A點移動到B點,如果鼠標移動得足夠快,那么就可以理解為從A直接到B而不經過中間的任何一個點,那么mouseMove函數僅僅調用一次。反之鼠標慢慢從A移動到B,那么中間可能會經過C、D、E、F......一系列的點,而mouseMove也會被執行多次。總之當鼠標在組件上移動時,mouseMove會頻繁被調用,之所以閃爍問題出在redraw方法,如果redraw方法不加任何參數那么將對組件全部重繪,對于鼠標托拽這種操作鼠標每移動一次就要對組件全部重繪,性能的代價可想而知,不閃才怪呢。解決的辦法就是只重繪變化的部分。
    如何做到這一點,要先了解必要的繪圖機制,在SWT中(Swing繪圖原理與其類似)redraw其實會包含2個含義,擦除、繪制,所以得名于redraw,意思就是重繪。首先是根據傳入redraw方法的參數確認需要擦除的范圍,如果沒有則擦除全部,然后底層會創建一個繪制請求交給操作系統去處理,但是這個請求不會在redraw方法調用完畢后立即被處理,即重繪操作不會立即執行,它是被送進底層的事件隊列中。處理時的繪制操作是由paintControl完成,所以redraw的調用會導致paintControl的執行。
    再將那兩行代碼列出來。
    redraw(tempLocation, 0, TEMP_H.getBounds().width,
         TEMP_H.getBounds().height, false);
       tempLocation = valueToPels(getValue()) + movedX
         - TEMP_H.getBounds().width / 2;
    如果光標從A移到B處,redraw方法所做的事情是通知重繪光標在A點時虛擬圖標范圍內的圖像,然后立即創建一個繪制請求送到系統事件隊列,然后更新tempLocation的值。現在再將繪制的那部分代碼列出
    if (controlPoint != null) {
         gc.drawImage(TEMP_H, tempLocation, 0);
        }
    將這兩部分代碼列出來做個比較,還有非常重要的一點需要指出,托拽時虛擬光標能呈連續性移動,這一功能之所以能得以實現非常重要的一點是:從底層請求送入事件隊列到請求被執行存在時間差,利用這個時間差(時間非常短)可以執行一些“更新操作”,比如上面的更新tempLocation。簡單的理解是redraw方法調用會以異步方式執行擦除、繪制。當執行繪制操作“gc.drawImage(TEMP_H, tempLocation, 0);”時,tempLocation已經是更新后的值了,而redraw表明擦除舊區域的圖像,因為當paintControl執行時這部分圖像區域根據計算結果已經不再是虛擬滑塊了。下面做一個試驗。
    添加下面紅色代碼
    redraw(tempLocation, 0, TEMP_H.getBounds().width,
         TEMP_H.getBounds().height, false);
       try {
        Thread.sleep(1000);
       } catch (InterruptedException e1) {
        e1.printStackTrace();
       }
       tempLocation = valueToPels(getValue()) + movedX
         - TEMP_H.getBounds().width / 2;
    這樣在繪制執行時,tempLocation還沒有得到更新,效果運行下知曉。

    現在,你可以運行完整的程序了,看一下托拽時的效果。
    完整的程序這里下載

    posted on 2007-10-23 13:35 sun_java_studio@yahoo.com.cn(電玩) 閱讀(13124) 評論(13)  編輯  收藏 所屬分類: NetBeansSWT

    評論

    # re: SWT自定義組件之Slider 2007-10-23 13:42 BeanSoft

    我來頂一下!  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-10-30 11:22 Matthew Chen

    redraw(tempLocation, 0, TEMP_H.getBounds().width,TEMP_H.getBounds().height, false);
    是只重繪temp thumb原來的區域,因為slider本身添加自己為PaintListener,故重畫的時候paintControl被調用,那么paintControl方法作用的是整個圖形區域還是temp thumb原來的區域?如果是后者,那新的temp thumb所在的區域由誰在何時繪制? 如果是前者,重繪的不還是整體而不是thumb的區域嗎?  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-10-30 12:47 Matthew Chen

    進一步觀察發現,lz的代碼運行時虛擬滑塊的邊緣往往缺失,結合上一條評論談到的,我想移動滑塊時重

    繪的是移動操作的上一次tempLocation指出的舊滑塊的區域,而我們想要看到的新滑塊的區域,并沒有全部被納入重畫的范圍,修改的方法:
    redraw(tempLocation, 0, TEMP_H.getBounds().width,TEMP_H.getBounds().height, false);
    tempLocation = valueToPels(getValue()) + movedX - TEMP_H.getBounds().width / 2;
    redraw(tempLocation, 0, TEMP_H.getBounds().width,TEMP_H.getBounds().height, false);
    還沒想到更好的方法。  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-10-30 15:47 sun_java_studio@yahoo.com.cn(電玩)

    @Matthew Chen
    paintControl方法作用的是整個圖形區域,也就是說畫是整個區域的重畫,但是擦除如果是整個區域擦除的話那屏幕就會閃了,你可以將redraw(tempLocation, 0, TEMP_H.getBounds().width,TEMP_H.getBounds().height, false); 這行代碼理解為“擦除”,擦除原來區域的圖像(在執行擦除前,原來區域的區域的圖像是舊虛擬滑塊,等到操作系統執行繪制時,那部分區域已不是虛擬滑塊了),新的temp thumb繪制是在paintControl方法完成的。
    畫是整個區域的重畫,擦是部分部分被擦。  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-10-30 15:51 sun_java_studio@yahoo.com.cn(電玩)

    @Matthew Chen
    大可不必調用redraw(tempLocation, 0, TEMP_H.getBounds().width,TEMP_H.getBounds().height, false)兩次。
    開始我也是這么寫的,后來改進只在tempLocation賦值后調用,我運行程序的時候沒發現邊緣缺失的現象。  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-10-30 19:39 Matthew Chen

    我截了圖
    http://www.tkk7.com/Files/djsl6071/桌面.rar
    第一張是正常情況,第2,3張分別顯示向上、下拖動時出現的缺失。
    你可以用windows自帶的圖片查看器打開,并放大,仔細看虛擬滑塊的邊緣。  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-10-30 22:25 sun_java_studio@yahoo.com.cn(電玩)

    @Matthew Chen
    虛擬滑塊的圖片本來就是圓角矩形,輪廓是用虛線勾出來的,你替換其他圖片試試,不應該是程序的原因。  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-11-20 11:14 黑巧克力

    LZ的虛擬滑塊確似有缺失的現象,當朝一個方向滑動時,那個方向最邊緣的虛線顯示不出來。當垂直方向移動一下鼠標便可以顯示出來。  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-11-20 16:59 電玩

    @黑巧克力
    My god.代碼實現中 水平 和垂直 是互逆的,怎么會出現垂直正常,水平有缺失的情況呢。如果缺失的話不妨采用+1像素長度試試,只能靠微調了。  回復  更多評論   

    # re: SWT自定義組件之Slider 2007-11-20 18:07 黑巧克力

    redraw(tempLocation-1, 0, TEMP_H.getBounds).width+2,TEMP_H.getBounds().height, false);
    似乎在兩邊多加一個象素就可以畫全了
      回復  更多評論   

    # re: SWT自定義組件之Slider 2007-11-20 18:08 黑巧克力

    @電玩
    我說的垂直是垂直于slider的方向,大概是運動的時候redraw少畫的象素。  回復  更多評論   

    # re: SWT自定義組件之Slider 2014-04-11 09:47 Angus

    這個DEMO還有嗎?DEMO和源碼能否發過來學習下?謝謝
    jaykkll@126.com  回復  更多評論   

    # re: SWT自定義組件之Slider 2014-04-11 09:49 Angus

    主要要想做一個雙滑塊的,能設置滑塊長度或者同時讀取雙滑塊VALUE的SLIDER
    望發來看看
    謝謝  回復  更多評論   

    TWaver中文社區
    主站蜘蛛池模板: 免费的涩涩视频在线播放| 无码精品人妻一区二区三区免费| 成在人线av无码免费高潮水 | 中文无码日韩欧免费视频| 国产免费131美女视频| 亚洲精品永久在线观看| 黑人粗长大战亚洲女2021国产精品成人免费视频 | 插鸡网站在线播放免费观看| 亚洲一区二区精品视频| www免费黄色网| 亚洲精品乱码久久久久久按摩 | 免费精品一区二区三区在线观看| 亚洲1234区乱码| 四虎www成人影院免费观看| 亚洲av纯肉无码精品动漫| 国产gav成人免费播放视频| 美女被免费网站视频在线| 亚洲片一区二区三区| 国产午夜成人免费看片无遮挡| 亚洲AV无码一区东京热久久| 久久午夜伦鲁片免费无码| 亚洲国产日韩在线成人蜜芽| 成人免费视频试看120秒| 免费一级毛suv好看的国产网站 | 在线视频网址免费播放| 亚洲精品无码mv在线观看网站 | 亚洲色图综合在线| 四虎国产精品免费永久在线| 亚洲AV日韩AV永久无码免下载| 午夜免费福利小电影| 国产a v无码专区亚洲av| 久久久久国产精品免费看| 亚洲国产精品第一区二区| 无码一区二区三区免费| 亚洲欧洲日产韩国在线| 亚洲一级免费毛片| 亚洲一卡一卡二新区无人区| 最近高清中文字幕无吗免费看| 亚洲另类无码专区丝袜| 亚洲精品国产高清嫩草影院| 成人无码视频97免费|