2018年2月22日 星期四

[軟體] 會場音樂播放器 arena music player

這是為了公司春酒/尾牙使用的音樂播放軟體,用 Powerbuilder 10 開發,使用多部筆記型電腦撥放,搭配我自己自製的混音器使用。



這個播放器可以預載 16 軌音樂,與 8 組音效,基本春酒/尾牙流程是足夠了,不過不含影片的播放,因為雖然可以開發MCI Video Player,但是我一直處理不好字幕檔的問題,所以播放軟體我就改成使用 potPlayer 來處理。而這支程式純粹是用來播放多軌音樂的

這播放器的好處是不需要在資料夾內一個一個點擊開啟音樂,然後又要關閉播放器的問題。
每一軌音樂都可以暫停,然後再播放其它音樂,當另一個音樂流程結束,再繼續原本的音樂。



本程式由 PB 10 開發,如果你沒有安裝過 PB10 的 Runtime,需先安裝下面 Runtime 包

要注意幾點:

2018年2月9日 星期五

powerbuilder send message control

Powerbuilder 對於視窗形物件的控制比起 C/C++/C#/VB 等開發工具弱勢多了。

但是,Powerbuilder 提供了 Send 函數來彌補對視窗物件的一些細微控制。
其實,它是源自於 Windows 本身對於標準 Windows 物件的控制,它是使用了 Message ID 方式通知物件進形事件動作,而 Powerbuilder 只是順便利用了這個設計而已。

例如:ListView 為人詬病的就是缺了 Scroll 的控制,在資料量大時,一定會超過一個畫面的資料,會跑到畫面之外,此時你只能用滑鼠去 Scroll 或是用滑鼠操作 Scroll bar ,沒有辦法用 Programming 方式控制。

此時,就可以利用 Windows Message 方式發送控制資訊給 Windows物件 來控制:


//捲動功能,可適用在ListView、DataWindow、MulitiLine Editor上
//上捲一行
Send(Handle(lv_mylist), 277, 0, 0)
//上捲一頁
Send(Handle(lv_mylist), 277, 2, 0)
//下捲一行
Send(Handle(lv_mylist), 277, 1, 0)
//下捲一頁
Send(Handle(lv_mylist), 277, 3, 0)

//像是下面兩種方式都可觸發按鈕Click事件
Send(Handle(Parent), 273, 0, Handle(cb_OK))
cb_OK.TriggerEvent(Clicked!)

//最小化 DataWindow
Send(Handle(dw_whatever), 274, 61472, 0)
//最大化 DataWindow
Send(Handle(dw_whatever), 274, 61488, 0)
//一般大小 DataWindow
Send(Handle(dw_whatever), 274, 61728, 0)



2018年1月23日 星期二

Xamarin : Android 取得媒體儲存裝置 retrieve emulated or removable storage path

這是一個很觀念的問題,其實android對於儲存位置的概念跟Windows是不一樣的。

我們所知的 Windows 的儲存媒體,通常都會是以『磁碟機』(Device)概念呈現的,每個 Device 都會被賦予一個『磁碟機代號』(Device Letter),像是 『C:\』『 D:\』 等等。

但是除了 Windows 以外的世界,基本上會是用『路徑』來呈現,由其 Android 的 Dalvik 系統本身就是 java 的支線,所以儲存空間會像是 『/Storage/dev0』『/Storage/dev1』之類的呈現法。

瞭解了以上的概念之後,再來瞭解 Android 對於儲存空間的認定。

一般我們在看儲存位置的時候,直覺上會把手機的儲存空間分為『內建』『內部』『外部』『擴充』等等的用法,因為 Android 有分為內建記憶空間和一個可以擴充的 SD 卡(或是TF卡)。

而我們程式在開發時相關文件都會去 Google 找類似 Internal 或是 External 類的單字搜尋,但實際上找到的文件範例基本上都會牛頭不對馬嘴,最大的原因應該會出在 Android 開發團隊對於這種一般人認定的常規式用法式有很大出入的。

Android 對於內建記憶空間和擴充的SD卡認定如下:

Emulated - 內部記憶空間,用的單字是『仿真』,而不是 Internal (內部),是因為 Android 真正內部記憶體指的是ROM,也就是作業系統OS存在的地方,而ROM本身不能經常寫入,因為有寫入壽命限制,但是作為一個作業系統難免會有許多設定或變更或紀錄,那這樣要存在哪裡呢?事實上, Android 手機真正可以讀寫的內建空間其實也是一個 SD 卡,指是手機製造商會在手機裡面直接燒上SD的儲存晶片作為內建儲存空間,凡是OS以外的程式APP或是紀錄都會存在此處,所以把這種內建 SD 記憶體稱為『仿真記憶體』。

Removable - 擴充的記憶體(SD卡或TF卡),這被稱為『可移除式』媒體,名字簡而易瞭,代表這記憶體可以被移除或是安插。

External - 而這個『外部』的意思就是泛指除了 OS 的 ROM 之外的都會稱為 External,所以上面兩個『Emulated』、『Removable』都算在 External 下面。而 External 下只有一個 storages,從這個 storages 下才會開始區分各類儲存空間。

所以記憶體歸類會像下面結構:


先看看程式樣本,再來解說:

2018年1月11日 星期四

Xamarin : Android : FilePicker 檔案瀏覽

其實這是用來搞懂 這一篇 Browse Files - Xamarin
範例則來自 GitHub 的 mgmclemore 收集的 A collection of Xamarin.Android sample projects

由於 android 瀏覽檔案時沒有像 Windows 一般有『現成』的檔案瀏覽介面(SHELL)可以使用,所以就非常麻煩的必須自己去作出像檔案瀏覽器一樣的介面。

而且 Xamarin 開發的介面中也沒有可以直接使用的元件,所以這完全必須依賴外部套件才有辦法作出來。
這個套件引用了 Xamarin.Android.Support.v4 這個套件,所以必須先到NuGet去下載這個套件到專案內。

2017年11月29日 星期三

Xamarin : android : 停用按鈕聲音

正常的狀況下,按下Android APP 內的按鈕 Button,裝置都會發出『達、達』『滴、滴』等的聲音,這個聲音不是 APP 產生或是開發出來的。

這是 Android 內置的系統聲音。

如果某些情境開發下,希望能夠關閉這個聲音,可以透過下面兩種方式實現:

1.針對按鈕 Button 操作屬性
程式方式:


button.setSoundEffectsEnabled(false);

屬性變更


<Button
    ...
    android:soundEffectsEnabled="false" />



2.針對全環境(APP)操作:
在資源檔內建立 res/values/styles.xml 樣式檔




<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="AppBaseTheme" parent="android:Theme.Black.NoTitleBar">
    <!--
        Theme customizations available in newer API levels can go in
        res/values-vXX/styles.xml, while customizations related to
        backward-compatibility can go here.
    -->
</style>

<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
    <!-- All customizations that are NOT specific to a particular API-level can go here. -->
    <item name="android:soundEffectsEnabled">false</item>
</style>

</resources>



然後,在 AndroidManifest.xml 裡面,修改(不存在就增加)這一段,強制使用樣式檔


<application
    ...
    android:theme="@style/AppTheme" >



參考:
https://stackoverflow.com/questions/15293608/disable-button-click-sound-in-android
https://stackoverflow.com/questions/5023170/how-to-disable-default-sound-effects-for-all-my-application-or-activity


2017年11月27日 星期一

Xamarin : android : Animation 特效 - 按鈕動畫測試

如果版面上需要讓 View (控件) 有點特效,其實 android 有提供一些基本2D的特效可以使用,這個就是 Animation。
※先說明Animation在這裡是一種效果,並非我們認知的那種動畫,所以無法設計太多一連串的動作,因此通常比較適合『咻一下子』這種感覺。

1 . 要在要運行的 Activity 引入

Using Android.Views.Animations

2 . 然後在右邊Resource資料夾下建立一個anim資料夾(這個請勿隨意命名,因為在android套件是有關聯的,隨意命名會找不到)



3 . 然後在anim資料夾下建立你的預訂好的動畫(特效)檔案,它基本上就是一個XML檔案。


這個檔案的詳細內容寫法可以參考下面網站:
官方網站(詳細但太多太雜,很容易找不到重點):https://developer.xamarin.com/guides/android/application_fundamentals/graphics_and_animation/
極客學院(簡單易懂,但無太多詳細解釋):http://wiki.jikexueyuan.com/project/android-animation/1.html

2017年11月24日 星期五

Xamarin : Android : 自訂繼承控制項 Coustom class inherit by TextView or other

很多時候,原始的控制項功能無法滿足社計上的需要時就必須修改控制項了。

這個範例是繼承自 TextView 的自訂元件。

1.首先到專案內新增一個類別 (新增項目→C#類別),名稱為 MyTextView.cs


2.首先要引用下面這個類別,才能使用 IAttributeSet


using Android.Util;


3.然後讓這個類別繼承 View,然後要實現相關的 建構子


namespace TestAnimator
{
    //讓 MyTextView 繼承自 TextView
    public class MyTextView : TextView
    {
        //實現基本的建構子
        public MyTextView(Context context) : base(context) { }
        public MyTextView(Context context, IAttributeSet attributeSet) : base(context, attributeSet) { }
        public MyTextView(Context context, IAttributeSet attributeSet, int defaultStyle) : base(context, attributeSet, defaultStyle) { }


4.接下來,我希望這個 TextView 有一個 MeMe 的屬性,而這屬性是 int 型別,當這個 MeMe 數值改變時,我希望能夠顯示在 Text 上。

因此在 class 下再建立一個內部儲存變數 _meme 與屬性 public int MeMe,然後覆寫 Draw 事件讓它可以把變數內容寫入 Text


        //內部變數
        private int _meme = 0;
       
        //屬性設定
        public int MeMe
        {
            get { return _meme; }
            set
            {
                _meme = value;
                //Invalidate是用來觸發 Draw 功能的
                this.Invalidate();
            }
        }


        //覆寫 Draw 事件

        public override void Draw(Canvas canvas)
        {
            base.Draw(canvas);
            //顯示到 Text 上
            this.Text = _meme.ToString();
        }


5.到此,存檔,基本的繼承型自訂元件雛型就完成,然後準備把它加到我們的 Main.axml 上。


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/linearLayout2">
        <TextView
            android:text="ObjectAnimator:"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/label2" />
        <TestAnimator.MyTextView
            android:text="0"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/textView2" />
    </LinearLayout>
                        .
                        .
                        .


但是.....看似沒問題的時候,IDE卻發出了警告

這個大意是『找不到我們自訂的元素』,怎麼回事?

應該是這個自訂的 MyTextView.cs 是 C# 的, 而 java 並不認識它

所以,我們就利用 Android.Runtime.Register 把它導出給 java 看得到。

回到 MyTextView.cs 裡,並在最上方面引用


using Android.Runtime;


然後在 class 前面寫入 Register


namespace TestAnimator
{
    // 用 Register 導出類別
    [Register("TestAnimator.MyTextView")]
    public class MyTextView : TextView
    {
        public MyTextView(Context context) : base(context) { }
        public MyTextView(Context context, IAttributeSet attributeSet) : base(context, attributeSet) { }
        public MyTextView(Context context, IAttributeSet attributeSet, int defaultStyle) : base(context, attributeSet, defaultStyle) { }
                        .
                        .
                        .


存檔後,再回到 Main.axml 上看看,就會發現它已經可以找到我們自訂的類別元素了。

其他:
如果,這個自訂類別需要被 ObjectAnimator 呼叫並自動寫入 MeMe 屬性,通常都會失敗。

研究其原因,發現 Android 某些可以更改屬性值的函數,它在更改函數時不是使用:

object.PropertyName = newValue

而是使用 Method "方法"去改變屬性值,像這樣:

object.setPropertyName(newValue);

一個可能被其他類別操作的的屬性都會附帶 get/set 操作方法

而我這邊案例是做給 ObjectAnimator 處理的,所以我只會實做 setPropertyName 方法

回到 MyTextView.cs 裡,我們找到 MeMe 屬性的程序碼下方加入:


        public void setMeMe(int value)
        {
            _meme = value;
            // 內容變更時要刷新畫面
            this.Invalidate();
        }


此時,如果你已經準備好 ObjectAnimator  的程序片段就可以去試試看是否會成功。


            //對MyTextView的MeMe屬性設定數字 0 到100 的變化
            var tv2 = FindViewById<MyTextView>(Resource.Id.textView2);
            ObjectAnimator objectAnimator = ObjectAnimator.OfInt(tv2, "MeMe", 1, 100);
            objectAnimator.SetDuration(1000);
            objectAnimator.RepeatCount = ObjectAnimator.Infinite;
            objectAnimator.Start();


結果,又不如預期,無法執行!

為什麼?

研究發現,其原因還是跟 TestAnimator.MyTextView 無法被 Main.axml 找到一樣

需要導出!!!!!!

類別裡面的方法要導出不同於類別是使用 Export 指令。

使用前要先確定 『專案』→『參考』底下是否有 『Mono.Android.Export』,如果沒有請加入參考:

再回到 MyTextView.cs 裡,前面加入


using Java.Interop;


然後在 setMeMe(int value) 前面 Export 它(注意大小寫,java對大小寫很敏感)


        [Export("setMeMe")]
        public void SetMeMe(int value)
        {
            _meme = value;
            this.Invalidate();
        }


存檔後,再去執行 ObjectAnimator  程式碼,發現可以用了耶!!