上色

Code Block

2016年6月3日 星期五

[.Net] 讀取CSV出現亂碼

當CSV檔中含有中文時,使用StremReader讀取通常都會出現亂碼,這是因為.Net預設使用Unicode編碼,但通常Excel還是使用Big5編碼,此時只要以下列方式指定系統編碼即可解決;
System.IO.FileStream fs = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
System.IO.StreamReader sr = new System.IO.StreamReader(fs, System.Text.Encoding.Default);
使用System.Text.Encoding.Default指定系統預設編碼為StremReader的編碼格式。

[.Net] 字串轉為DateTime

通常需要將字串轉為DateTime的時候,一大問題就是來源格式常常不是固定的格式,而是有各種五花八門的格式,這時候就可以利用以下方式進行轉換:

首先定義可能出現的各種日期格式,可參照https://msdn.microsoft.com/en-us/library/8kb3ddd4(VS.71).aspx

string[] dateTimeList = { 
 "yyyy/M/d tt hh:mm:ss", 
 "yyyy/MM/dd tt hh:mm:ss", 
 "yyyy/MM/dd HH:mm:ss", 
 "yyyy/M/d HH:mm:ss", 
 "yyyy/M/d", 
 "yyyy/MM/dd",
 "M/d HH:mm:ss"
}; 

接著就可以開始處理字串,使用CultureInfo.InvariantCulture解析不同國家語言格式的字串(或是套用目前的國家地區設定解析,CultureInfo.CurrentCulture),使用DateTimeStyles.AllowWhiteSpaces避免字串中可能出現的無意義空白;此外為了避免依然轉換錯誤,可以將整段敘述加入到Try...Catch區塊中,再於Catch區塊中對轉換錯誤加以處理。

string DateTime date;
try
{
 date = DateTime.ParseExact(dateString, dateTimeList, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces);
 transferFlag = true;
}
catch (Exception e)
{
 throw new Exception("日期格式錯誤: " + e.Message);
}

Ref: https://dotblogs.com.tw/chhuang/2008/03/18/1921

2016年2月1日 星期一

[Android] Android 6.0 (marshmallow) run-time permission 執行期間權限取得

自Android 6.0開始,除了必須在AndroidManifest.xml中宣告權限,在安裝時告知使用者外,執行期間需要用到特殊權限的話也必須告知使用者並取得。

權限可區分為以下兩大類

  1. 一般權限(PROTECTION_NORMAL),亦即在AndroidManifest.xml中宣告即可,在程式安裝時即授予程式該權限,用戶也無法手動取消。在Android 6.0(API 23)中,一般權限列表如下︰
    • ACCESS_LOCATION_EXTRA_COMMANDS
    • ACCESS_NETWORK_STATE
    • ACCESS_NOTIFICATION_POLICY
    • ACCESS_WIFI_STATE
    • BLUETOOTH
    • BLUETOOTH_ADMIN
    • BROADCAST_STICKY
    • CHANGE_NETWORK_STATE
    • CHANGE_WIFI_MULTICAST_STATE
    • CHANGE_WIFI_STATE
    • DISABLE_KEYGUARD
    • EXPAND_STATUS_BAR
    • GET_PACKAGE_SIZE
    • INTERNET
    • KILL_BACKGROUND_PROCESSES
    • MODIFY_AUDIO_SETTINGS
    • NFC
    • READ_SYNC_SETTINGS
    • READ_SYNC_STATS
    • RECEIVE_BOOT_COMPLETED
    • REORDER_TASKS
    • REQUEST_INSTALL_PACKAGES
    • SET_TIME_ZONE
    • SET_WALLPAPER
    • SET_WALLPAPER_HINTS
    • TRANSMIT_IR
    • USE_FINGERPRINT
    • VIBRATE
    • WAKE_LOCK
    • WRITE_SYNC_SETTINGS
    • SET_ALARM
    • INSTALL_SHORTCUT
    • UNINSTALL_SHORTCUT
  2. 需要執行期間授權的權限,用戶也可以在設定中隨時關閉他們,Android將之分為數個類別,當取得類別中的其中一個權限時,也就等於取得該類別所有權限,分類如下︰

若要取得權限,需在程式碼中加入
ActivityCompat.requestPermissions(Activity activity, String[] permissions, int requestCode)

若要確認是否具有權限,可使用
ContextCompat.checkSelfPermission (Context context, String permission)

而當使用者拒絕授予此權限時,開發者可能會需要說明該權限的用途並再次詢問,這時可使用
ActivityCompat.shouldShowRequestPermissionRationale(Activity activity, String permission)
當使用者拒絕授予權限時,下次調用此函式就會返回true,開發者可在此時加入權限說明。

當使用者選擇授予或拒絕此權限時,會呼叫onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)方法,此時override此方法即可處理需要權限的操作。


具體程式碼如下(以讀寫SD卡為例)︰
    

private final int REQUEST_CODE_ASK_PERMISSIONS = 10;

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
 switch (requestCode) {
  case REQUEST_CODE_ASK_PERMISSIONS:
   if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    // Permission Granted
    //執行需要讀寫SD卡的操作
   } else {
    // Permission Denied
    Toast.makeText(MainActivity.this, getString(R.string.noPermission), Toast.LENGTH_LONG).show();
   }
   break;
  default:
   super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 }
}

private void checkPermission(){
 //讀取關於讀寫外部儲存裝置的權限
 int hasReadExternalStoragePermission = ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
 //若沒有權限則詢問取得
 if (hasReadExternalStoragePermission!= PackageManager.PERMISSION_GRANTED)
 {
  if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE)){
   //當被使用者拒絕後再次詢問時,說明該權限用途,用戶可選擇重試(重新詢問是否授予)或取消(維持不授予權限)
   new AlertDialog.Builder(MainActivity.this)
     .setMessage("該權限用以讀寫SD卡")
     .setPositiveButton("重試", new DialogInterface.OnClickListener() {
      @Override
      public void onClick(DialogInterface dialog, int which) {
       ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_ASK_PERMISSIONS);
      }
     })
     .setNegativeButton("取消", null)
     .create()
     .show();
   return;
  }
  ActivityCompat.requestPermissions(MainActivity.this,new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},REQUEST_CODE_ASK_PERMISSIONS);
 }
 else
  //已有權限,直接執行需要讀寫SD卡的操作
}

Reference: Android developer documents

2015年12月14日 星期一

[Android] 偵測方向使用不同layout

若要偵測使用者螢幕方向,並使用對應不同xml的layout,可使用下列方式︰

  1. 設計兩種不同layout的xml檔案。
  2. 在AndroidManifest.xml中要改變的activity中新增屬性︰
  3. android:configChanges="orientation|keyboardHidden|screenSize"
  4. 如此一來當方向或螢幕大小改變的時候,就會觸發︰
    onConfigurationChanged(Configuration newConfig)
  5. 依照類似下面的方式判斷長寬,以決定適用的layout file。
    public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            Display display = getWindowManager().getDefaultDisplay();
            int width;
            int height;
            Point size = new Point();
            display.getSize(size);
            width=size.x;
            height=size.y;
            if (width>height) { //Landscape
                setContentView(R.layout.activity_main_land);
                initView();
            }
            else{
                setContentView(R.layout.activity_main_port);
                initView();
            }
        }
    

2015年12月11日 星期五

[Android] Shell Tool for root commands


import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Scanner;

import android.content.res.AssetManager;
import android.os.Build;
import android.util.Log;

public class ShellUtils
{
 private static String busyboxPath="/system/xbin/busybox";

 /**
  * Set custom busybox path
  * @param path Path
  */
 public static void setCustomBusybox(String path){
  busyboxPath = path;
 }
 
 public static boolean checkBusyboxPath(){
  try{
   if (!new File("/system/xbin/busybox").exists()){
    if(!new File("/system/bin/busybox").exists())
     return false;
    else busyboxPath="/system/bin/busybox";
   }
            else busyboxPath="/system/xbin/busybox";
  }catch (SecurityException e){
   return false;
  }
  return true;
 }

 /**
  * Try to get root permission
  * @return true means success; false means fail
  */
 public static boolean rootCheck(){
  //List all the files under "/data/data", doing this operation needs root permission
  //CommandResult commandResult = execCommand(false,true, "ls /data/data/");
  //Empty result means there's no root permission
        //return !commandResult.successMsg.isEmpty();
  return execCommand(false,true,"").result==0;
 }

 @SuppressWarnings("deprecation")
 public static boolean initiateBusybox(){
  //Clear old files
  File busybox = new File(GlobalVariable.getContext().getFilesDir()+"/busybox");
  if (busybox.exists())
   busybox.delete();
  //Copy corresponding busybox to internal folder
  String cpuVersion;
  if (android.os.Build.VERSION.SDK_INT <21)
   cpuVersion= Build.CPU_ABI;
  else
   cpuVersion=Build.SUPPORTED_ABIS[0];
  //Determine busybox version
  String filename;
  cpuVersion=cpuVersion.toLowerCase();
  if (cpuVersion.contains("arm"))
   filename="busybox-armv5l";
  else if (cpuVersion.contains("x86"))
   filename="busybox-i686";
  else
   return false;
  AssetManager assetManager = GlobalVariable.getContext().getAssets();
  InputStream in = null;
  OutputStream out = null;
  try {
   in = assetManager.open(filename);
   out = new FileOutputStream(busybox);
   byte[] buffer = new byte[1024];
   int read;
   while ((read = in.read(buffer)) != -1) {
    out.write(buffer, 0, read);
   }
  }
  catch (IOException e) {
   e.printStackTrace();
   return false;
  }
  finally {
   if (in != null) {
    try {
     in.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
   if (out != null) {
    try {
     out.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }
  //Make busybox executables
  ShellUtils.execCommand(false, true, "chmod 0755 " + GlobalVariable.getContext().getFilesDir() + "/busybox");
  return true;
 }

 /**
  * Execute commands and get the results
  * @param isBusybox Run cmds via busybox or not
     * @param isRoot Run cmds as root?
  * @param cmds Command(s)
  * @return Results of commands
  */
 public static CommandResult execCommand(boolean isBusybox, boolean isRoot, String...cmds){
        int result=-1;
        StringBuilder successMsg = null;
        StringBuilder errorMsg = null;
        if (cmds==null || cmds.length==0)
            return new CommandResult(result,null,null);
  try{
            Process proc=Runtime.getRuntime().exec(isRoot?"su":"sh");
   DataOutputStream opt = new DataOutputStream(proc.getOutputStream());
   boolean exitCmdFound=false;
   //Write cmds
   for(String cmd:cmds){
                //Using busybox and check its path
    if (isBusybox)
     opt.writeBytes(busyboxPath + " ");
    //Unless make sure cmd is in English, using UTF-8 coding, avoid wrong coding.
    opt.write(cmd.getBytes(Charset.forName("utf-8")));
    if(!exitCmdFound && cmd.equals("exit"))
                    exitCmdFound=true;
    opt.writeBytes("\n");
   }
   if(!exitCmdFound)
                opt.writeBytes("exit\n");
   opt.flush(); 
            //Get results
            result=proc.waitFor();
            successMsg = new StringBuilder();
            errorMsg = new StringBuilder();
            Scanner scanner;
            scanner=new Scanner(new InputStreamReader(proc.getInputStream()));
            while (scanner.hasNextLine())
                successMsg.append(scanner.nextLine());
            scanner=new Scanner(new InputStreamReader(proc.getErrorStream()));
            while (scanner.hasNextLine())
                errorMsg.append(scanner.nextLine());
  } catch (Exception e){
            e.printStackTrace();
        }
        return new CommandResult(result,successMsg==null?null:successMsg.toString(), errorMsg==null?null:errorMsg.toString());
 }

    public static class CommandResult {

        /**
         * Result of command
         * 0: normal
         * else: error
         */
        public int    result;
        public String successMsg;
        public String errorMsg;

        public CommandResult(int result, String successMsg, String errorMsg) {
            this.result = result;
            this.successMsg = successMsg;
            this.errorMsg = errorMsg;
        }
    }
}

2015年7月31日 星期五

[C#] Load custom cursor from resources

Sometime you need a custom cursor for your application, the following step let you use your custom cursor in the resources.


  1. Add cursor files (*.cur) to resources
    • In [Properties] -> [Resources] tab, click [Add Resource] -> [Add Existing File...]
    • Choose your custom cursors and click OK.
  2. Load cursor with following code:
this.Cursor = new Cursor(new System.IO.MemoryStream(Properties.Resources.MyCursor));
, where Mycursor refers to your cursor name.

2015年7月25日 星期六

[C#] 取得picture box在zoom模式時的正確圖片座標

private Point unScale(Point scaledP)
{
 if (picturebox1.SizeMode != PictureBoxSizeMode.Zoom) //only zoom mode need to scale
  return scaledP;
 Point unscaled_p = new Point();
 // image and container dimensions
 int w_i = picturebox1.Image.Width;
 int h_i = picturebox1.Image.Height;
 int w_c = picturebox1.Width;
 int h_c = picturebox1.Height;
 float imageRatio = w_i / (float)h_i; // image W:H ratio
 float containerRatio = w_c / (float)h_c; // container W:H ratio

 if (imageRatio >= containerRatio)
 {
  // horizontal image
  float scaleFactor = w_c / (float)w_i;
  float scaledHeight = h_i * scaleFactor;
  // calculate gap between top of container and top of image
  float filler = Math.Abs(h_c - scaledHeight) / 2;
  unscaled_p.X = (int)(scaledP.X / scaleFactor);
  unscaled_p.Y = (int)((scaledP.Y - filler) / scaleFactor);
 }
 else
 {
  // vertical image
  float scaleFactor = h_c / (float)h_i;
  float scaledWidth = w_i * scaleFactor;
  float filler = Math.Abs(w_c - scaledWidth) / 2;
  unscaled_p.X = (int)((scaledP.X - filler) / scaleFactor);
  unscaled_p.Y = (int)(scaledP.Y / scaleFactor);
 }
 return unscaled_p;
}