2019年1月29日 星期二

php 的日期比較

在比對之前,先說明一下如何得到目前日期 在 php 5.2(含) 前可以用 date('Y-m-d') 在 php 5.2(不含) 後可以用 new DateTime() 雖然函數有一點點不同,但都需要注意時區可能產生的問題

接下來開始比較日期
使用函數 strtotime
$Date1 = date('Y-m-d'); 
$Date2 = '9999-12-31';
if(strtotime($Date1) > strtotime($Date2)) { 
  echo 'Date1 較新'; 
} 
else { 
  echo 'Date2 較新'; 
} 
基本上這樣就可以了,但是若是使用XAMPP,上面的語法可能會出現錯誤,原因在於 Y2K38漏洞,也被稱為Unix Millennium Bug,也就是 strtotime 只認得 2038-1-19 03:14:07 之前的日期時間,在此日期時間後的就會溢位,進而產生錯誤。

所以將語法修改如下,順便用了 php 5.2 版之後的語法
$Date1 = date_format(new DateTime(),'U'); 
// $Date2 = date_format(new DateTime('9999-12-31'),'U');
// 這邊需要注意因為 DateTime()會包含時間的部分,所以在比較時要同時加上時間的部分(或去除時間的部分)來比較會較正確,
// 或是將 $Date2 多加一天後再比較也行,所以將 $Date2 修改一下
$Date2 = date_format(date_modify(new DateTime('9999-12-31'),'+1 days'),'U');
if($Date1 > $Date2) { 
  echo 'Date1 較新'; 
} 
else { 
  echo 'Date2 較新'; 
} 
藍色小舖 php 討論區- 日期的比較 PHP轉換超過2038年的日期出錯問題解決

用Temp Table取代Cursor

cursor通常用來逐筆處理資料使用,下面是一個簡單的範例(MS SQL Server 2008)
declare @myId int
declare @myName nvarchar(20)
declare @myCursor CURSOR

set @myCursor = CURSOR FAST_FORWARD
FOR
SELECT ID, NAME FROM Employee
open @myCursor
INTO @myId, @myName
WHILE @@FETCH_STATUS = 0
BEGIN

    --DO SOMETHING

    FETCH NEXT FROM @myCursor
    INTO @myId, @myName

END

CLOSE @myCursor
DEALLOCATE @myCursor
用temp table來模擬cursor的操作模式,簡單的說就是把SELECT ID, NAME FROM Employee的結果存到temp table中,在逐步讀取,所以必須先建立一個temp table如下
create table #tempEmployee
(
    ID int,
    NAME nvarchar(20)
)
然後把資料insert into select 到 #tempEmployee
insert into #tempEmployee (ID,NAME)
select ID,NAME
from Employee
接下來,就是逐步讀取這個temp table #tempEmployee
在MS SQL Server中使用SET ROWCOUNT 1來控制select時一次只撈出一筆資料,而且每做完一筆就刪掉,直到#tempEmployee沒有資料為止
declare @countTemp int --用來計算#tempEmployee還剩幾筆資料

--計算#tempEmployee資料數
select @countTemp = count(*) from #tempEmployee

while(@countTemp > 0)
begin
    set rowcount 1
    select @myId = ID, @myName = NAME from #tempEmployee
    --To Something
     
    --因為set rowcount 1的關係,所以一次只會刪一筆
    delete from #tempEmployee 

    --計算還剩幾筆,@countTemp > 0繼續
    select @countTemp = count(*) from #tempEmployee  
end

--#刪除 tempEmployee
drop table #tempEmployee

--把select的預設比數恢復正常
set rowcount 0
如此一般,完整的程式碼如下
declare @myId int
declare @myName nvarchar(20)

create table #tempEmployee
(
    ID int,
    NAME nvarchar(20)
)

declare @countTemp int --用來計算#tempEmployee還剩幾筆資料

--計算#tempEmployee資料數
select @countTemp = count(*) from #tempEmployee

while(@countTemp > 0)
begin
    set rowcount 1
    select @myId = ID, @myName = NAME from #tempEmployee
    --To Something
     
    --因為set rowcount 1的關係,所以一次只會刪一筆
    delete from #tempEmployee 

    --計算還剩幾筆,@countTemp > 0繼續
    select @countTemp = count(*) from #tempEmployee  
end

--#刪除 tempEmployee
drop table #tempEmployee
遜砲賴的爆肝筆記-stored procedure中不使用cursor逐步讀取資料列的方法

2018年4月29日 星期日

不爽 0002

總: 董事長剛吃完飯,等等讓他吃藥
我: 好(看了一下桌面),只有一個杯子?
總: 對,你到底有沒有在顧(用心)?飯後都只有一杯藥
我: 有啊(心想早上明明看到2個杯子的藥,還是我看錯了?)
(之後)
總: 你...
我: 嗯(不想回答有意義的文字了)


(中午,問了一下護理師)
我: 請問早上餐後的藥是一杯還兩杯?
護: 兩杯呵,一杯裡面三顆藥,一杯喝的
我: 謝謝。(心想,掯!早上總經理是在唸不爽的嗎?到底是誰沒在顧?)

不爽 0001

早上總經理帶來早餐(一袋兩個餐+另一袋一杯飲料)
總: 看你要吃什麼餐
我: 隨手拿了一個吃完後
總: 有飲料
我: 打開一看只有一杯,你喝什麼?
總: 你喝
(我拿出來時不小心打翻飲料,桌上地上都有)
總: 龜龜毛毛(一手拿早餐,一手拿在處理飲料)
我: 我自己處理
總: 不用,代誌都不會做
(我待了幾秒後,就不爽的離開)
(過一會兒,總經理來電,我不想接,直接回來)
總: 突然跑去哪裡?
我: (沒回應,因為不爽,心想有必要這樣罵人嗎?)

2018年4月3日 星期二

Windows Form 將 FormBorderStyle 設為 none 時,視窗的拖曳方式

因為專案畫面上的需求(Form 表頭的顏色與設計),
所以將 Windows Form 的 FormBorderStyle 設定 none 後,再自行加上 TableLayoutPanel (tableLayoutPanel1) 等控制項來偽裝

此時如果遇上使用者使用的電腦解析度,低於設計時,會造成畫面被切掉,此時因為 FormBorderStyle 設為 none,所以 Form 也拉不動,所以需要再對於拖曳功能加工(沒事找事做)
對於 Form 內的控制項可以單純的在 MouseDown(按下滑鼠)、DragEnter(拖曳開始)、DragDrop(拖曳結束) 事件中寫下相對應的語法,可參考 在 Windows Form 中執行拖放作業

但現在是在 Windows Form 外執行拖放作業,可以用win32 的角度來撰寫語法
// win32 的角度來撰寫語法
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;

[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();

// 再在 tableLayoutPanel1 的 MouseDown 事件寫下
private void tableLayoutPanel1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{     
    if (e.Button == MouseButtons.Left)
    {
        ReleaseCapture();
        SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
    }
}
或是在 tableLayoutPanel1 的 MouseDown、MouseMove 事件撰寫語法
// 在 tableLayoutPanel1 的 MouseDown、MouseMove 事件撰寫語法
private Point startPoint;   // 紀錄目前視窗的位置
private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e)
{
    //當滑鼠擊以左點擊控制項的範圍內時,透過計算紀錄目前視窗的位置
    if (e.Button == MouseButtons.Left)
    {
        startPoint = new Point(-e.X + SystemInformation.FrameBorderSize.Width, -e.Y - SystemInformation.FrameBorderSize.Height);
    }
}

private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
{
    // 當滑鼠擊按著左鍵移動時,記錄下移動的位置
    if (e.Button == MouseButtons.Left)
    {
        // 滑鼠指標的位置
        Point mousePos = Control.MousePosition;
        // 新視窗的位置(等於滑鼠指標目前的位置與先前視窗位置的位移)
        mousePos.Offset(startPoint.X, startPoint.Y);
        // 改變視窗位置
        Location = mousePos;
    }
}

2018年3月2日 星期五

BackgroundWorker 的使用

專案需求:一支常駐程式可以撈資料
想法大概是:一個 Timer 定期檢查、一個 ProgressBar 顯示進度、一個 backgroundworker 避免影響使用者、一個 NotifyIcon 可以出現桌面的右下角
程式寫法大致上是:

新增一個 Form,在上面放一個 ProgressBar
然後拉一個 Timer
一個 BackgroundWorker,並設定 WorkerReportsProgress = true,才可以修改 ProgressBar 的進度 然後在初始化後設定 form 的位置
public Form1()
{
    InitializeComponent();

    // 縮小視窗,以觸發 notifyIcon (不直接觸發是為了保留之後若要點擊縮小的 Icon 時可以恢復視窗)
    this.WindowState = FormWindowState.Minimized;
    this.TopLevel = true;
    this.screenWidth = Screen.PrimaryScreen.Bounds.Width;
    this.screenHeight = Screen.PrimaryScreen.Bounds.Height;
    //this.Location = new Point(screenWidth - Width - 5, screenHeight);

    //設定視窗位置在右下角最高可以顯示的位置, 工具列上方
    this.stopHightLocation = Screen.PrimaryScreen.Bounds.Height - (Screen.PrimaryScreen.Bounds.Height - Screen.PrimaryScreen.WorkingArea.Height + this.Height);
    this.Location = new Point(screenWidth - Width - 5, stopHightLocation);
}
在 BackgroundWorker (Name 設為 bw) 的事件上分別寫上
void bw_DoWork(object sender, DoWorkEventArgs e)
{
    object myParameter = e.Argument; // myParameter 是帶過來要用的參數
    // 開始執行要很久的動作
    ...(略)
    // 結束
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // 把視窗叫起來讓使用者看到進度條
    if (this.WindowState == FormWindowState.Minimized)
    {
        this.Show();    // 顯示視窗
        this.WindowState = FormWindowState.Normal;
    }
    // 修改進度條的進度
    progressBar1.Value = e.ProgressPercentage;
    // 以文字的方式顯示進度條的百分比
    label2.Text = string.Format("{0:f0}%", (progressBar1.Value / Convert.ToDouble(progressBar1.Maximum)) * 100);
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    this.WindowState = FormWindowState.Minimized;   // 隱藏視窗
    timer1.Enabled = true;  // 工作完成了,再次啟用 timer1
}
再來在 form load 事件內啟用 timer1
private void timer1_Tick(object sender, EventArgs e)
{
    // 如果背景程式忙碌中,就停止這次的背景
    if (bw.IsBusy)
        return;

    timer1.Enabled = false;  // 然後將 timer 停用

    // 這邊可以收集要的參數
    // 若收集參數也很耗時,則可以另開一個backgroundworker來做,然後在backgroundworker_completed事件內執行主要的忙碌工作
    // 或是將收集參數的工作也被到bw裡面,但此時可能不單純只顯示progressbar,可能還需要顯示進度的內容說明

    // 然後呼叫backgroundworker 執行 (可以加判斷是否真的需要呼叫背景程式,減少觸發次數)
    if (myParameter != null)
        bw.RunWorkerAsync(myParameter); 
}
後來看到另一篇文章寫到,原來也可以在 BackgroundWorker_Completed 事件中,再次執行耗時的工作即可,減少使用一個 timer 元件,
把timer1_Tick()事件用另一個function 包起來,然後稍微修改一下
void PrepareToRunWorkerAsync()
{
    // 如果背景程式忙碌中,就停止這次的背景
    if (bw.IsBusy)
        return;

    // 這邊可以收集要的參數
    // 若收集參數也很耗時,則可以另開一個backgroundworker來做,然後在backgroundworker_completed事件內執行主要的忙碌工作
    // 或是將收集參數的工作也被到bw裡面,但此時可能不單純只顯示progressbar,可能還需要顯示進度的內容說明

    // 然後呼叫backgroundworker 執行
    bw.RunWorkerAsync(myParameter); 
}
然後在 Form1() 建構式中直接執行上述的 function
public Form1()
{
    InitializeComponent();

    // 縮小視窗,以觸發 notifyIcon (不直接觸發是為了保留之後若要點擊縮小的 Icon 時可以恢復視窗)
    this.WindowState = FormWindowState.Minimized;
    this.TopLevel = true;
    this.screenWidth = Screen.PrimaryScreen.Bounds.Width;
    this.screenHeight = Screen.PrimaryScreen.Bounds.Height;
    //this.Location = new Point(screenWidth - Width - 5, screenHeight);

    //設定視窗位置在右下角最高可以顯示的位置, 工具列上方
    this.stopHightLocation = Screen.PrimaryScreen.Bounds.Height - (Screen.PrimaryScreen.Bounds.Height - Screen.PrimaryScreen.WorkingArea.Height + this.Height);
    this.Location = new Point(screenWidth - Width - 5, stopHightLocation);

    PrepareToRunWorkerAsync();
}
修改一下 BackgroundWorker_Completed 事件
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    this.WindowState = FormWindowState.Minimized;   // 隱藏視窗
    // 若使用者沒有下取消命令,則在Completed 事件下再次執行耗時的工作
    if (e.Cancelled == false)
        PrepareToRunWorkerAsync();

}
當然上述做法需再搭配一個啟動耗時工作的事件,不然一但取消後,就只有重開執行檔一途了
void buttonStart(object sender, EventArgs e)
{
    PrepareToRunWorkerAsync();
}
參考:C# 使用 BackgroundWorker 背景執行

2018年2月23日 星期五

動態控制 ContextMenuStrip 不出現

通常在按下滑鼠右鍵要出現選單時,都會用到 ContextMenuStrip 這個控制項,使用方式如下:
1.從工具箱點兩下 ContextMenuStrip,產生 contextMenuStrip1
2.點擊該 contextMenuStrip1,可以編輯清單內容
3.選擇 contextMenuStrip1 要出現在哪個控制項,在該控制項的 ContextMenuStrip 屬性上選擇 contextMenuStrip1 

但是當要動態控制 contextMenuStrip1 要不要出現時,
無法寫成
// 假設 contextMenuStrip1 要出現在 treeView1 的右鍵時
treeView1.ContextMenuStrip = "";
只能在 contextMenuStrip1.Opening 事件上動手腳
boolean bContextMenuStripVisible = false; // 指定是否顯示 ContextMenuStrip
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
    if (bContextMenuStripVisible == false)
    {               
          e.Cancel = true;
    }
}