上色

Code Block

2018年7月9日 星期一

[.Net] 以string,Join串接array of array(jagged array)

大家都知道使用string.Join可以串接陣列或清單中字串,像是下面這樣的形式
string[] source = new string[]{"one", "two", "three"};
string result = string.Join(",", source);

但是當陣列中還有陣列(jagged array, e.g. array of array)的時候怎麼辦呢?
這時候LINQ的Select語法就派上用場了,一層一層的串上去就對了
string[][][] source = new string[][][]{
 new string[][]{
  new string[]{"1", "2", "3"},
  new string[]{"one", "two", "three"}
 },
 new string[][]{
  new string[]{"4", "5", "6"},
  new string[]{"five", "six", "seven"}
 }
};

string result = string.Join(",", source.Select(m => string.Join(",", m.Select(n => string.Join(",", n)))));

如此一來就可以成功串接每一層陣列中的字串了。

Reference:
https://stackoverflow.com/questions/35102320/c-sharp-copying-jagged-arrays-to-strings


2018年7月4日 星期三

[.Net] Office interop runtime無法執行

紀錄一下最近遇到的問題,使用C#於MS Office 2013的環境上開發了一個程式,於MS Office 2010的機器上運作正常,但拿到MS Office 2007的機器上運作就會報錯。

首先,PIAs(Primary Interop Assemblies)必須與想要支援的最低版本相容,亦即想要支援Office 2007的話就必須使用v12的PIAs。
所以必須在Project的reference中加入v12版本的office PIAs(可以使用NuGet直接找到v12版本的PIA,亦即Microsoft.Office.Interop(v12)加入);但是光這樣是不夠的,因為當你的機器上有安裝更新版Office(如Office 2013)的時候,編譯時候會自動redirect到新版的PIAs,也就是在本機的GAC(global assembly cache)中所安裝的版本。

要修正這個問題,必須停止所謂的assembly binding redirection,可依照下列步驟進行:

1. 打開C:\Windows\Assembly\GAC或C:\Windows\Assembly\GAC_MSIL資料夾(根據你的windows版本而定)。
2. 找到對應的PIA,如我要使用Word的PIA,就找到Policy.12.0.Microsoft.Office.Interop.Word這樣的資料夾。
3. 在資料夾中找到設定的xml檔案,將下面這段註解掉
<bindingredirect newversion="14.0.0.0" oldversion="12.0.0.0"></bindingredirect>
變成
<!--<bindingredirect newversion="14.0.0.0" oldversion="12.0.0.0"></bindingredirect>-->
如此一來就會不會在redirect到新版本的PIAs,可以達到在不同機器上的相容性。

Reference:
https://stackoverflow.com/questions/6984733/office-2007-pia

2018年6月28日 星期四

[.Net] Embedded dll into exe

1. Add dll to resources, set the build action to "embedded resources" 2. Add following code to "Program.cs"
static void Main()
{
 //load resource dll
 AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

 Application.EnableVisualStyles();
 Application.SetCompatibleTextRenderingDefault(false);
 Application.Run(new frmTDSCreator());
}

private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
 string resourceName = "TDSCreator.Resources." + new AssemblyName(args.Name).Name + ".dll";
 using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
 {
  byte[] assemblyData = new byte[stream.Length];
  stream.Read(assemblyData, 0, assemblyData.Length);
  return Assembly.Load(assemblyData);
 }
}

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();
            }
        }