2010年12月19日星期日

Cocoa runtime working directory

To specify a working directory of the application at runtime, we could use the 
changeCurrentDirectoryPath:(NSString*) of the NSFileManager

where the [NSBundle mainBundle] get the mainBundle and in my case I need the  "resourcePath" to get the path containing my stuffs.

2010年12月8日星期三

Export NSView to PNG

The following code is for exporting a NSView to PNG file.


And you can change the NSPNGFileType for other format, of course.

KVC for BOOL type not working

Recently I tried to read xml files by KVC in order to get rid of the frustrating hard-coded name matching.

However, by using the  -setValue:forKeyPath: function, I got a "-[NSCFString charValue]: unrecognized selector sent to instance XXXXXX" error when it comes to setting value of any property of BOOL type.

I could only found a few reference on Stackoverflow.

@try
    {
        [(NSObject*)retObj setValue:[[obj keyValuePairs] objectForKey:key]
                         forKeyPath:key];
    }
    @catch (NSException * e)
    {
        if ([[e name] isEqualToString:NSInvalidArgumentException])
        {
            NSNumber* boolVal = [NSNumber numberWithBool:[[[obj keyValuePairs] objectForKey:key] boolValue]];
            [(NSObject*)retObj setValue:boolVal
                             forKeyPath:key];
        }
    }

Since I am not knowing the type until runtime, I need to catch the exception before converting the data type. But I am not sure whether any other type would cause the same exception and lead to an incorrect casting.

I haven't met any problem yet, and hopefully it is safe

2010年12月4日星期六

NSImage Rotate at Image Centre

I have a much more clear concept of the coordinate system of Cocoa through these two days' work.

Firstly, the drawRect function of NSView doesnt simply draw on top of the context. The z-index couldn't be determined. Thus sometimes things that were drawn first may not covered by things drawn later. The reason behind, after googleing it, is that NSView is not for overlapping but subviewing to each other to form a view hierarchy.
So, in my case for a map editor, each component should not be regard as a subview in the main view representing the map. Instead they should be drawn directly on the drawRect function of the main view.

Secondly, here is the sample code of rotating a image.(not 100% complete as I not yet handle the case that the angle is 0, but should be pretty easy :p).

The concept would be better described by illustration but forgive my laziness.
Brief steps of rotating a image at its center:
1. create a NSBezierPath base on the original image bound
2. transform the NSBezierPath to get the bound after the image rotated
3. create an NSImage with the size equal to the size of the rotated bound
4. center the image in the rotated bound
5. set the NSAffineTransform: move to center of the bound > rotate > move back to the original position
6. lock the focus on the newly created image and draw the original image to its bound. done=]


In the drawRect function, calibrate the rect of the image. Otherwise the image will not be "anchored" on its center

Don't know whether there exists a better method (sth like simply putting the image in a NSImageView and rotate the view). But it at least works for me

2010年12月2日星期四

NSImage Transformation - Flip

Today a lot of time was wasted on deal with image transformation due to lack of investigation about the properties of drawing in Cocoa.

I can't make the transform context effect only on the image I want to transform but the whole NSView owning the image. After using "lockfocus", nothing is drawn becox of the wrong implementation of the coordination system.
The pain ended after I kw about the difference between "bounds" and "frame" on Stackoverflow

To make the "lock" function, it seems that the "frame" need to be set correctly and after the "lock" the drawing is easy.

Here is a simple sample of flipping a NSImage with the NSAffineTransform class. The image is drawn from its middle bottom.

2010年11月22日星期一

Few little technique on Objective-C

To test an object's type in runtime in Obj-C:

[myObject isKindOfClass:[NSString class]]

To get the mouse position on the screen:

NSPoint mouseLoc;
mouseLoc = [NSEvent mouseLocation];


To get the mouse position wrt local window:

NSPoint  point   = [self convertPoint:[theEvent locationInWindow] fromView:nil];

2010年11月18日星期四

Strong Type NSArray?

Is there any way to make NSArray become strong typed? The answer is no. (except subclassing it)

From Stackoverflow

Some ppl say it is not necessary, provided having good practice and good testing coverage.
After that I start to think of: why should I need a complier? why should I need VS or Xcode or Eclipse?
Maybe it really takes some time for me to indulge in the embrace of Apple.

2010年11月17日星期三

Cocoa NSCollectionView

Just found a useful tutorial at André's Blog when making the map editor for the new project(we are tired at making the map xml files manually).

Even it is "for dummies", it takes me some time to follow as a newcomer to Cocoa and Mac OS. The version difference even make it worser as something has changed in Xcode.

tbc

Objective C Property

http://www.cocoacast.com/?q=node/103

2010年11月2日星期二

Create Mac ICNS file for Xcode project

devdaily.com看到PNG to ICNS的教學,

FastIcns是一個方便的application, 可輕鬆把png 轉換成icns (mac的icon格式)


在Xcode裡, 把icns file 加到project中Resources/Icons & Default中
target 上 right click > GetInfo > Properties tab > Icon File 欄中輸入file的名稱,完成!=]

FastIcns project page

Converting audio file format on Mac by "afConvert"

"afConvert" is a command which could be directly used in the Terminal (I am using Mac OS X 10.6.4).

I used to convert audio file of format .m4a to .caf, which is a file format for iPhone and is actually a wrapper for a number of data formats like .mp3, LEI, acc, etc...

To make my sound file playable by my OpenAL class in iPhone SDK, I tried to change it to LEI16
"afConvert -f caff -d LEI16@44100 inputname outputname"

In order to make the files smaller, I tried using LEI8 and acc but the sounds played are weird.
I am still working on it.

Here is a shellScript for batch conversion, name the script as converttocaf.sh and all the file in that directory will be converted to the .caf format:
for f in *; do
if  [ "$f" != "converttocaf.sh" ]  then
    /usr/bin/afconvert -f caff -d LEI16@44100 $f
    echo "$f converted"
fi
done

2010年11月1日星期一

Mac Shell Script

In our project, the screen delay whenever any sound is played.
The reason is that it used AVAudioPlayer for playing all sounds and musics.
However I found that AVAudioPlayer is more for streaming but not caching, we need to shift to OpenAL for playing sounds.

After writing a class for OpenAL, I used much time to find out why it doesn't work.
The final answer is: it works, but it is not compatible with the original audio format ".m4a".
Then I need to find a way to convert them to ".caf" format which works well with OpenAL in iPhone.

Well, the next step is to find a way to do batch convertion with afConvert.
Finally comes to the topic: using shellScript on Mac.

(the above topics: OpenAL, afConvert to caf, afConvert batch convertion, will be discussed later)

Here is a gd answer on StackOverFlow (as always).

And it is a script for deleting all files with extension .caf in a directory: "find . -type f -name "*.caf" -exec rm -f {} \;"

2010年10月7日星期四

2010年9月24日星期五

C# Producer-Consumer Threading

由於有大量資料要從Database query出來, 但又不想使User Interface 停下來, 在Stackoverflow得到幫助, 用Producer-Consumer model 來解決

流程是: User Interface 需要query資料 >> producer 就produce一個delegate(就是c++ 的function pointer) >> 一大堆的query(其實是fucntion), 就由consumer來執行 >> 等所有有關的query都做完, 就invoke main thread(interface的thread) 的有關control refresh一下, 讓它顯示query了回來的資料

e.g. 有一個label要顯示用戶名稱

labelA.Text = queryUserName(userId, labelA);

public static string queryUserName(uint id, Control control)
{
    QueryFunction threadStart = delegate { StaticRes.queryUserNameThread(id, control); };
    Program.g_QueryThread.Enqueue(threadStart);
    return "Querying User Name...";
}

StaticRes.queryUserNameThread 就是雖要時間較長, 用來query資料的function

以下是class QueryThread的implementation:

public delegate void QueryFunction();

/// <summary>
/// Act as a consumer to the queries produced by the DataGridViewCustomCell
/// </summary>
public class QueryThread
{
    private struct QueueItem
    {
        public Delegate _target;
        public UInt64 _id;

        public QueueItem(Delegate target, UInt64 id)
        {
            _target = target;
            _id = id;
        }
    }
    private Object _syncEvents = new Object();
    private Queue<QueueItem> _queryQueue = new Queue<QueueItem>();
    private EventWaitHandle _waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);

    Producer queryProducer;
    Consumer queryConsumer;

    public QueryThread()
    {
        queryProducer = new Producer(_queryQueue, _syncEvents, _waitHandle);
        queryConsumer = new Consumer(_queryQueue, _syncEvents);

        Thread producerThread = new Thread(queryProducer.ThreadRun);
        Thread consumerThread = new Thread(queryConsumer.ThreadRun);

        producerThread.IsBackground = true;
        consumerThread.IsBackground = true;

        producerThread.Start();
        consumerThread.Start();
    }

    public void Enqueue(Delegate item, UInt64 id)
    {
        QueueItem queueItem = new QueueItem(item, id);
        _queryQueue.Enqueue(queueItem);

        _waitHandle.Set();
    }
}

按下來就是producer 跟consumer thread
producer主要是fire event 使consumer thread 開始digest那些query
consumer就是執行enqueue了的query function

class Producer
{
    private readonly Queue<QueueItem> _queue;
    private Object _sync;
    private EventWaitHandle _handle;

    public Producer(Queue<QueueItem> q, Object sync, EventWaitHandle waitHandle)
    {
        _queue = q;
        _sync = sync;
        _handle = waitHandle;
    }
    
    public void ThreadRun()
    {
        lock (_sync)
        {
            while (true)
            {
                //wait until item is enqueued
                _handle.WaitOne();

                //enqueued, tell worker thread
                if (_queue.Count > 0)
                {
                    Monitor.Pulse(_sync);
                    Monitor.Wait(_sync,0);
                 }
             }
        }
    }
}

class Consumer
{
    private readonly Queue<QueueItem> _queue;
    private Object _sync;

    public Consumer(Queue<QueueItem> q, Object sync)
    {
        _queue = q;
        _sync = sync;
    }

    public void ThreadRun()
    {
        lock (_sync)
        {
            Delegate query;
            while (true)
            {
                while (_queue.Count == 0)
                {
                    if (Program.g_CustomDialog.Visible == true)
                    {
                        Program.g_CustomDialog.DialogResult = DialogResult.OK;
                    }

                    Monitor.Wait(_sync);

                }

                QueueItem item = _queue.Dequeue();
                query = item._target;
                query.DynamicInvoke(null);
            }
        }
    }
}

後來有人在stackoverflow上說 lock後的 while(true) loop 會容易引致deadlock
由於我的threading implementation比較簡單, 就不作改動了, 下次記緊就好

msdn上對c# threading的教學: How to: Synchronize a Producer and a Consumer Thread
但不要跟他的implementation, 看一看這裡

2010年9月10日星期五

Code Syntax HighLight on Blogger

Code Highlighting on Blogger (from CraftyFella's Blog)

Add the following code before the </head> tag in the css template of your Blogger
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/>
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCpp.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCss.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJava.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPhp.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPython.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushRuby.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushSql.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushVb.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPerl.js' type='text/javascript'></script>
<script language='javascript'>
SyntaxHighlighter.config.bloggerMode = true;
SyntaxHighlighter.config.clipboardSwf = 'http://alexgorbatchev.com/pub/sh/current/scripts/clipboard.swf';
SyntaxHighlighter.all();
</script>

How to use:
<pre class="brush: csharp">
// Comment
public class Testing {
public Testing() {
}
 
public void Method() {
/* Another Comment
on multiple lines */
int x = 9;
}
}
</pre>

Escaped HTML:
Before posting, try to translate it into escaped html is better
Convert Raw HTML to Escaped HTML

2010年9月9日星期四

[.NET C#] DataTable GROUP BY

由於有大量Transaction資料(超過十萬行)在資料庫, 同時想做多個不同的統計, 為免加重資料庫的負荷, 唯有一次過query所需資料, 再在client那邊進行統計運算

Google一下後, 發現.net中的DataTable竟然沒有GROUP BY的功能
找了一些用foreach loop + Rows.Add 來自行製作一個新Table的教學
類似:
private DataTable GroupBy(DataTable myTable)
{
    DataTable dt = new DataTable();
    dt.Columns.Add("ColumnName");
    foreach (DataRow row in myTable.Rows)
    {
        string s = row["ColumnName"].ToString();
        DataRow[] drx = dt.Select("ColumnName= '" + s + "'");
        if (drx.Length == 0)
        {
            dt.Rows.Add(new object[] { s });
        }
    }
    return dt;
}

可是這麼一來就每一項統計都要自行製作一套Group by的運作, 延續性太低, 於是我在arstechnica forum發現了一個有用的function, 就拿了來改改, 比較dynamic一點點

public struct StatGroupByInfo
{
    public string table_Filter;
    public string[] column_FilterExpr;
    public string[] column_names;
    public string[] column_functions;
    public Type[] column_types;
    public string[] group_by;       //also sort by the first group_by column

    public StatGroupByInfo(string table_filter, string[] col_filters, string[] col_names, string[] col_functions, Type[] col_type, string[] group)
    {
        table_Filter = table_filter;
        column_FilterExpr = col_filters;
        column_names = col_names;
        column_functions = col_functions;
        column_types = col_type;
        group_by = group;
    }
}

static class DataSetFunctions
{
    public static DataTable GroupBy(DataTable table, StatGroupByInfo info)
    {
        return GroupBy(table, info.table_Filter, info.column_FilterExpr, info.column_names, info.column_functions, info.column_types, info.group_by);
    }

    public static DataTable GroupBy(DataTable table, string table_filter, string[] filterExpr, string[] aggregate_columns, string[] aggregate_functions, Type[] column_types, string[] group_by_columns)
    {
        //Filter the table
        DataView dvView = new DataView(table, table_filter, group_by_columns[0] + " ASC", DataViewRowState.CurrentRows);

        //Group the table by column
        DataTable dtGrouped = dvView.ToTable(true, group_by_columns);

        //Implement aggregate functions
        for (int i = 0; i < aggregate_columns.Length; i++)
        {
            dtGrouped.Columns.Add(aggregate_columns[i], column_types[i]);
        }

        foreach (DataRow row in dtGrouped.Rows)
        {
            List<string> filter_parts = new List<string>();

            for (int i = 0; i < group_by_columns.Length; i++)
            {
                filter_parts.Add(String.Format("[{0}] = '{1}'", group_by_columns[i], row[group_by_columns[i]].ToString().Replace("'", "''")));
            }

            string filter = String.Join(" AND ", filter_parts.ToArray());

            for (int i = 0; i < aggregate_columns.Length; i++)
            {
                string columnFilter = filter;
                if (!string.IsNullOrEmpty(filterExpr[i].Trim()))
                {
                    columnFilter = columnFilter + " AND " + filterExpr[i];

                    if (!string.IsNullOrEmpty(table_filter.Trim()))
                    {
                        columnFilter = table_filter + " AND " + columnFilter;
                    }
                }

                row[aggregate_columns[i]] = table.Compute(aggregate_functions[i], columnFilter);

                if (row[aggregate_columns[i]] == DBNull.Value && column_types[i] == typeof(uint))
                {
                    row[aggregate_columns[i]] = 0;
                }
            }
        }

        return dtGrouped;
    }
}

用法:
StatGroupByInfo groupByInfo = new StatGroupByInfo( "", new string[] { "[action] = 1 or [action] = 2",  "[action] = 3 or [action] = 4", "[action] = 5 or [action] = 6"}, new string[]{ "itemmallPoint", "nonItemmallPoint", "totalPoint" }, new string[]{ "sum(usePoint)", "sum(usePoint)", "sum(usePoint)" }, new Type[]{ typeof(uint), typeof(uint), typeof(uint) }, new string[]{ "date" } ),

DataTable _dt = DataSetFunctions.GroupBy(DbRes.Transaction, groupByInfo);

解說:
要customize 一個group by 的operation, 主要是用StatGroupByInfo裡的variables
public string table_Filter: 就是原來那個table的filter, 先濾掉不在統計範圍內的entry
public string[] column_FilterExpr: 每個column不同的filter
public string[] column_names: column的名稱, 可以自訂
public string[] column_functions: 每個column的aggregate function, 如 count, max, avg 等, 可參考MSDN
public Type[] column_types: 每個column的type, 要跟上面aggregate的return type對應
public string[] group_by: 要進行 GROUP BY 的columns


[後記]
後來發現有很多人寫的DataHelperClass, 用來manage dataset的data:
geekswithblogs一堆Helper Class 中的DataSetHelper Class that I am using
jiezhi@cnblogs
MSDN 上的一些文章

2010年9月6日星期一

C# Get Enum from Number or String

這裡找到一個由number/string 回傳 Enum 的 generic function

public static T StringToEnum<T>(string name)
{
  return (T)Enum.Parse(typeof(T), name);
}

public T NumToEnum<T>(int number)
{
   return (T)Enum.ToObject(typeof(T), number);
}

Example:
public enum DaysOfWeek
{
   Monday,
   Tuesday,
   Wednesday,
   Thursday,
   Friday,
   Saturday,
   Sunday
}

public enum MonthsInYear
{
   January,
   February,
   March,
   April,
   May,
   June,
   July,
   August,
   September,
   October,
   November,
   December
}

DaysOfWeek d = StringToEnum<DaysOfWeek>("Monday");
//d is now DaysOfWeek.Monday

MonthsInYear m = StringToEnum<MonthsInYear>("January");
//m is now MonthsInYear.January


如果Enum中沒有相對的value, 就會throw exception:
DaysOfWeek d = StringToEnum<DaysOfWeek>("Katillsday");
//throws an ArgumentException
//Requested value "Katillsday" was not found.

它提供了一個簡單的argument check, 這就行了:
if(Enum.IsDefined(typeof(DaysOfWeek), "Katillsday"))
    StringToEnum<DaysOfWeek>("Katillsday");


可是轉眼又看到有關performance及安全性的問題:The danger of over simplification
MSDN上也說不要用Enum.IsDefine(), M$大哥啊, 忽悠我啊?!

既然問題對我影響好像不大......就當過關了
但還是看了相關的資料, 說不定日後再要面對這個問題(希望不要)
C# Enum.Parse() Bug
More C# 'enum' Wackiness
Common Type System—Type Safety Limitations 這個讚

2010年9月3日星期五

C# Google Chart API

昨天找到一個for C#的 Google Chart API: GoogleChartSharp
很多東西都基本上齊全, 但裡面好像沒有提供 text format with scaling 的功能(找不到"chds"字串)
我做了個簡單的湊合一下

在Chart.cs裡加了一段 :

private string dataScale;
public void SetDataScale(int xLowerBound, int xUpperBound, int yLowerBound, int yUpperBound)
{
    this.dataScale += String.Format("{0},{1},{2},{3}", xLowerBound, xUpperBound, yLowerBound, yUpperBound);
}


protected virtual void collectUrlElements()
{
    .........

    // data scale
    if(dataScale != null)
    {
        urlElements.Enqueue(String.Format("chds={0}", this.dataScale));
    }

    .........
}
我後來才發現google提供的encode方法最高支援4095的int, 而且用extended encoding的話,  scaling好像是自動的, 即不能用"chds"字串來設定data scale("chds"只適用於text format)

所以只好把原來的data 乘以一個scalar(maxValue就是圖表資料中的最大值)

double scalar = 4095 / (double)maxValue;
dataValue *= scalar;
這樣就可(不知是不是完全)解決scaling的問題

2010年9月2日星期四

C# DataColumn To Array

在網上找到一段將 DataColumn 轉為 Array的Code
沒有Iteration, 很簡潔

String[] rowValuesForColumn =
    Array.ConvertAll<DataRow, String>(
    dataTable.Select(),
    delegate(DataRow row) { return (String) row[columnName]; }
 );