Updates from 一月, 2010

  • 自动从豆瓣获取专辑封面

    shishirui 15:07 on 2010/01/31 | 0 Permalink | Reply

    前几天看到 手气不错 写了一个 自动从豆瓣获取专辑封面的python脚本,感觉想法挺好,于是这几天我也利用空闲时间,用c#写了一个同样功能的小软件,希望对大家有用。

    功能:给MP3文件加上封面图片,加上封面图片后,在支持图片的播放器中播放时,就能显示出来。以下是用Windows Media Player播放时的样子:

    软件界面:

    目前发现有两种情况下获取不到封面图片:

    1. 豆瓣上搜索不到歌曲的封面时。
    2. MP3文件的ID3v1信息不全。

    所以如果大家看到有没加上封面的MP3时,请不要大惊小怪;)

    下载:getCover (64K)

    注:Windows XP的用户,可能需要安装 .NET Framework 才能运行。下载 .NET Framework .20

     
  • C#多线程操作界面控件的解决方案

    shishirui 21:57 on 2009/05/23 | 0 Permalink | Reply

    在.Net2.0环境上开发WinForm程序,当在多线程中修改界面控件的状态,会抛出一个异常:Cross-thread operation not valid:Control 'textBox1' accessed from a thread other than the thread it was created on .后查询知道这是因为.net 2.0以后加强了安全机制,不允许在winform中直接跨线程访问控件的属性。到网上Google下,找到的解决方案有三种,现将这三种解决方案整理如下:

    首先写下原先的代码:

    public partial class Form1 : Form
    {
    public Form1()
    {
    InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
    Thread thread = new Thread(ThreadFuntion);
    thread.IsBackground = true;
    thread.Start();
    }
    private void ThreadFuntion()
    {
    while (true)
    {
    this.textBox1.Text = DateTime.Now.ToString();
    Thread.Sleep(1000);
    }
    }
    }

    第一种解决方案:

    我们在Form1_Load()方法中加一句代码:

    private void Form1_Load(object sender, EventArgs e)
    {
    Control.CheckForIllegalCrossThreadCalls = false;
    Thread thread = new Thread(ThreadFuntion);
    thread.IsBackground = true;
    thread.Start();
    }
    加入这句代码以后发现程序可以正常运行了。这句代码就是说在这个类中我们不检查跨线程的调用是否合法(如果没有加这句话运行也没有异常,那么说明系统以及默认的采用了不检查的方式)。然而,这种方法不可取。我们查看CheckForIllegalCrossThreadCalls 这个属性的定义,就会发现它是一个static的,也就是说无论我们在项目的什么地方修改了这个值,他就会在全局起作用。而且像这种跨线程访问是否存在异常,我们通常都会去检查。如果项目中其他人修改了这个属性,那么我们的方案就失败了,我们要采取另外的方案。

    第二种解决方案:

    就是使用delegate和invoke来从其他线程中控制控件信息。网上有很多人写了这种控制方式,然而我看了很多这种帖子,表明上看来是没有什么问题的,但是实际上并没有解决这个问题,首先来看网络上的那种不完善的方式:

    public partial class Form1 : Form
    {
    private delegate void FlushClient();//代理
    public Form1()
    {
    InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
    Thread thread = new Thread(CrossThreadFlush);

    thread.IsBackground=true;
    thread.Start();
    }

    private void CrossThreadFlush()
    {
    //将代理绑定到方法
    FlushClient fc = new FlushClient(ThreadFuntion);
    this.BeginInvoke(fc);//调用代理
    }
    private void ThreadFuntion()
    {
    while (true)
    {
    this.textBox1.Text = DateTime.Now.ToString();
    Thread.Sleep(1000);
    }
    }
    }

    使用这种方式我们可以看到跨线程访问的异常没有了。但是新问题出现了,界面没有响应了。为什么会出现这个问题,我们只是让新开的线程无限循环刷新,理论上应该不会对主线程产生影响的。其实不然,这种方式其实相当于把这个新开的线程“注入”到了主控制线程中,它取得了主线程的控制。只要这个线程不返回,那么主线程将永远都无法响应。就算新开的线程中不使用无限循环,使可以返回了。这种方式的使用多线程也失去了它本来的意义。

    第三中解决方案,也是我推荐的解决方案:

    public partial class Form1 : Form
    {
    private delegate void FlushClient();//代理
    public Form1()
    {
    InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
    Thread thread = new Thread(CrossThreadFlush);
    thread.IsBackground = true;
    thread.Start();
    }

    private void CrossThreadFlush()
    {
    while (true)
    {
    //将sleep和无限循环放在等待异步的外面
    Thread.Sleep(1000);
    ThreadFunction();
    }
    }
    private void ThreadFunction()
    {
    if (this.textBox1.InvokeRequired)//等待异步
    {
    FlushClient fc = new FlushClient(ThreadFunction);
    this.Invoke(fc);//通过代理调用刷新方法
    }
    else
    {
    this.textBox1.Text = DateTime.Now.ToString();
    }
    }
    }

    运行上述代码,我们可以看到问题已经被解决了,通过等待异步,我们就不会总是持有主线程的控制,这样就可以在不发生跨线程调用异常的情况下完成多线程对winform多线程控件的控制了

     
  • 使用委托(C# 编程指南)

    shishirui 22:45 on 2009/05/20 | 0 Permalink | Reply

    委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。委托的类型由委托的名称定义。下面的示例声明了一个名为 Del 的委托,该委托可以封装一个采用字符串作为参数并返回 void 的方法。

    public delegate void Del(string message);

    构造委托对象时,通常提供委托将包装的方法的名称或使用匿名方法。实例化委托后,委托将把对它进行的方法调用传递给方法。调用方传递给委托的参数被传递给方法,来自方法的返回值(如果有)由委托返回给调用方。这被称为调用委托。可以将一个实例化的委托视为被包装的方法本身来调用该委托。例如:

    // Create a method for a delegate.
    public static void DelegateMethod(string message)
    {
    System.Console.WriteLine(message);
    }

    // Instantiate the delegate.
    Del handler = DelegateMethod;

    // Call the delegate.
    handler("Hello World");

    委托类型派生自 .NET Framework 中的 Delegate 类。委托类型是密封的,不能从 Delegate 中派生委托类型,也不可能从中派生自定义类。由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。有关更多信息,请参见何时使用委托而不使用接口。

    回调的另一个常见用法是定义自定义的比较方法并将该委托传递给排序方法。它允许调用方的代码成为排序算法的一部分。下面的示例方法使用 Del 类型作为参数:

    public void MethodWithCallback(int param1, int param2, Del callback)
    {
    callback("The number is: " + (param1 + param2).ToString());
    }

    然后可以将上面创建的委托传递给该方法:

    MethodWithCallback(1, 2, handler);

    在控制台中将收到下面的输出:

    The number is: 3

    在将委托用作抽象概念时,MethodWithCallback 不需要直接调用控制台 -- 设计它时无需考虑控制台。MethodWithCallback 的作用只是准备字符串并将该字符串传递给其他方法。此功能特别强大,因为委托的方法可以使用任意数量的参数。

    将委托构造为包装实例方法时,该委托将同时引用实例和方法。除了它所包装的方法外,委托不了解实例类型,所以只要任意类型的对象中具有与委托签名相匹配的方法,委托就可以引用该对象。将委托构造为包装静态方法时,它只引用方法。考虑下列声明:

    public class MethodClass
    {
    public void Method1(string message) { }
    public void Method2(string message) { }
    }

    加上前面显示的静态 DelegateMethod,现在我们有三个方法可由 Del 实例进行包装。

    调用委托时,它可以调用多个方法。这称为多路广播。若要向委托的方法列表(调用列表)中添加额外的方法,只需使用加法运算符或加法赋值运算符(“+”或“+=”)添加两个委托。例如:

    MethodClass obj = new MethodClass();
    Del d1 = obj.Method1;
    Del d2 = obj.Method2;
    Del d3 = DelegateMethod;

    //Both types of assignment are valid.
    Del allMethodsDelegate = d1 + d2;
    allMethodsDelegate += d3;

    此时,allMethodsDelegate 在其调用列表中包含三个方法 -- Method1、Method2 和 DelegateMethod。原来的三个委托 d1、d2 和 d3 保持不变。调用 allMethodsDelegate 时,将按顺序调用所有这三个方法。如果委托使用引用参数,则引用将依次传递给三个方法中的每个方法,由一个方法引起的更改对下一个方法是可见的。如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。如果委托具有返回值和/或输出参数,它将返回最后调用的方法的返回值和参数。若要从调用列表中移除方法,请使用减法运算符或减法赋值运算符(“-”或“-=”)。例如:

    //remove Method1
    allMethodsDelegate -= d1;

    // copy AllMethodsDelegate while removing d2
    Del oneMethodDelegate = allMethodsDelegate - d2;

    由于委托类型派生自 System.Delegate,所以可在委托上调用该类定义的方法和属性。例如,为了找出委托的调用列表中的方法数,您可以编写下面的代码:

    int invocationCount = d1.GetInvocationList().GetLength(0);

    在调用列表中具有多个方法的委托派生自 MulticastDelegate,这是 System.Delegate 的子类。由于两个类都支持 GetInvocationList,所以上面的代码在两种情况下都适用。

    多路广播委托广泛用于事件处理中。事件源对象向已注册接收该事件的接收方对象发送事件通知。为了为事件注册,接收方创建了旨在处理事件的方法,然后为该方法创建委托并将该委托传递给事件源。事件发生时,源将调用委托。然后,委托调用接收方的事件处理方法并传送事件数据。给定事件的委托类型由事件源定义。有关更多信息,请参见事件(C# 编程指南)。

    在编译时,对分配了两种不同类型的委托进行比较将产生编译错误。如果委托实例静态地属于类型 System.Delegate,则允许进行比较,但在运行时将返回 false。例如:

    delegate void Delegate1();
    delegate void Delegate2();

    static void method(Delegate1 d, Delegate2 e, System.Delegate f)
    {
    // Compile-time error.
    //Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f
    //is not the same as that of d.
    System.Console.WriteLine(d == f);
    }

     
  • 关于C#中ListBox控件重绘Item项

    shishirui 00:37 on 2009/05/10 | 0 Permalink | Reply

    “如何让ListBox的Item项显示多行?”,貌似没有人给出直接的答案。
    现在将一点个人经验总结如下:
    1、首先选中拖至面板的ListBox控件,点属性,选中DrawMode,改成OwnerDrawFixed或OwnerDrawVariable
    2、还是在属性工具中,切换到事件(就是那个闪电图标),鼠标双击‘行为’菜单下的DrawItem,添加一个事件。
    3、在‘窗体设计器生成的代码’中就添加了一个新的事件
    this.listBox1.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.listBox1_DrawItem);
    4、学过的朋友应该知道,这时代码里会有一个 listBox1_DrawItem()的方法,在里面写代码吧。

    private void listBox1_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
    {
    e.DrawBackground();
    Brush myBrush = Brushes.Black;  //初始化字体颜色=黑色
    this.listBox1.ItemHeight=90; //设置项高,根据具体需要设置值
    //为每个项设置字体颜色
    //如果不需要可以不写此switch
    switch (e.Index)
    {
    case 0:
    myBrush = Brushes.Red;
    break;
    case 1:
    myBrush = Brushes.Orange;
    break;
    case 2:
    myBrush = Brushes.Purple;
    break;
    case 4:
    myBrush = Brushes.White;
    break;
    }
    e.Graphics.DrawString(listBox1.Items[e.Index].ToString(), e.Font, myBrush,e.Bounds,null);
    //这句好象可以不要,自己试下
    e.DrawFocusRectangle();
    }

    其实,看过MSDN的朋友应该知道了,MSDN里有类似的代码,我不过修改了几个地方,
    加入了项高this.listBox1.ItemHeight=90,可以输入多行字符,这里要注意的是,
    整个ListBox的高应该是你设置的Item项高的倍数大一点点,不然ListBox在显示时会有所变形!
    (例如:我的Item项高是60,而ListBox要一次显示三项,所以设置为184)

     
  • 在 C# 中使用设置

    shishirui 15:52 on 2009/04/22 | 0 Permalink | Reply

    关键字:设置、Configuration

    摘要:了解如何配合应用程序及用户设置使用 Visual C# 2005 中的新功能。

    一、简介

    .NET Framework 2.0 允许您创建和访问在各应用程序执行会话之间保持的值。这些值称为设置。设置可以表示用户首选项,也可表示应用程序需要使用的宝贵信息。例如,可以创建一系列设置来存储应用程序配色方案的用户首选项。也可以存储指定应用程序所使用数据库的连接字符串。通过设置不但可以保持对于代码外部的应用程序至关重要的信息,而且还可以创建分别存储各用户首选项的配置文件。

    Visual Basic 2005 使用 My 命名空间提供了一种显而易见的设置访问机制,而在 Visual C# 2005 中没有类似的命名空间,因而访问设置稍微要困难一些。尽管如此,C# 用户仍可通过访问 Properties 命名空间来使用设置。在阅读本文的过程中,您将会了解应用程序设置与用户设置之间的差异、如何在设计时创建新的设置、如何在运行时访问设置,以及如何将多组设置合并到应用程序中。

    应用程序及用户设置

    二、设置具有四个属性:

    • Name(名称):设置的“Name”(名称)属性是指用于在运行时访问设置值的名称。
    • Type(类型):设置的“Type”(类型)是指设置所表示的 .NET Framework 类型。设置可以是任意类型。例如,存放用户颜色首选项的设置将会是 System.Color 类型。
    • Scope(作用域):“Scope”(作用域)属性表示如何在运行时访问设置。“Scope”(作用域)属性有两个可能的值:“Application”(应用程序)和“User”(用户)。本部分将会对这些值进行更多讨论。
    • Value(值):“Value”(值)属性表示访问设置时返回的值。该值将为“Type”(类型)属性所表示的类型。

    这些属性中的大多数都相当容易理解。“Name”(名称)、“Type”(类型)和“Value”(值)的概念均应为大多数程序员所熟知。 “Scope”(作用域)属性需要稍加说明。设置具有两个可能的作用域:应用程序作用域和用户作用域。具有应用程序作用域的设置表示无论用户首选项为何应用程序都会使用的设置,而具有用户作用域的设置对实际应用程序来说通常并不是很重要,它们很可能与首选项或其他非关键值关联。

    应用程序作用域设置与用户作用域设置之间的重要区别是,用户作用域设置在运行时为读/写,并且可在代码中对其值进行更改和保存。应用程序作用域设置在运行时为只读。虽然可以读取,但是不能对其进行写入。具有应用程序作用域的设置只能在设计时或通过手动修改设置文件进行更改。

    三、在设计时创建新设置

    可以使用设置设计器在设计时创建新的设置。设置设计器采用了大家熟悉的网格式界面,通过它可以创建新设置并指定这些设置的属性。必须为每个新设置指定 “Name”(名称)、“Type”(类型)、“Scope”(作用域)和“Value”(值)。创建了设置后,即可使用本文稍后介绍的机制在代码中对其进行评估。

    1. 在设计时创建新设置的步骤

    • 在“Solution Explorer”(解决方案资源管理器)中,展开项目的“Properties”(属性)节点。
    • 在“Solution Explorer”(解决方案资源管理器)中,双击要在其中添加新设置的 .settings 文件。此文件的默认名称是 Settings.settings。
    • 在设置设计器中,为设置设定“Name”(名称)、“Type”(类型)、“Scope”(作用域)和“Value”(值)。每行代表单个设置。

    2. 在设计时更改现有设置的值

    还可以按以下步骤所述,使用设置设计器在设计时更改预先存在设置的值:

    在设计时更改现有设置值的步骤

    • 在“Solution Explorer”(解决方案资源管理器)中,展开项目的“Properties”(属性)节点。
    • 在“Solution Explorer”(解决方案资源管理器)中,双击要在其中添加新设置的 .settings 文件。此文件的默认名称是 Settings.settings。
    • 在设置设计器中,找到要更改的设置,然后在“Value”(值)列中键入新值。

    3. 在应用程序会话之间更改设置值

    有时,在编译和部署了应用程序后,可能需要在应用程序会话之间更改设置值。例如,可能需要更改连接字符串,使其指向正确的数据库位置。由于设计时工具在应用程序编译和部署后不可用,所以必须手动在文件中更改设置值。

    在应用程序会话间更改设置值的步骤

    使用 Microsoft 记事本或其他某种文本或 XML 编辑器,打开与应用程序关联的 <AssemblyName>.exe.config 文件。

    找到要更改的设置条目。它应看似以下示例:

    <setting name="Setting" serializeAs="String">

    <value>这是设置值</value>

    </setting>

    为设置键入新值,然后保存该文件。

    4. 在运行时使用设置

    运行时应用程序可以通过代码使用设置。具有应用程序作用域的设置值能够以只读方式进行访问,而用户作用域设置的值可以进行读写。在 C# 中可以通过 Properties 命名空间使用设置。

    5. 在运行时读取设置

    可在运行时使用 Properties 命名空间读取应用程序作用域及用户作用域设置。Properties 命名空间通过 Properties.Settings.Default 对象公开了项目的所有默认设置。编写使用设置的代码时,所有设置都会出现在 IntelliSense 中并且被强类型化。因此,举例来说,如果设置的类型为 System.Drawing.Color,则无需先对其进行强制类型转换即可使用该设置,如下例所示:

    this.BackColor = Properties.Settings.Default.myColor;

    6. 在运行时保存用户设置

    应用程序作用域设置是只读的,只能在设计时或通过在应用程序会话之间修改 <AssemblyName>.exe.config 文件来进行更改。然而,用户作用域设置却可以在运行时进行写入,就像更改任何属性值那样。新值会在应用程序会话持续期间一直保持下去。可以通过调用 Settings.Save 方法来保持在应用程序会话之间对用户设置所做的更改。这些设置保存在 User.config 文件中。

    7. 在运行时写入和保持用户设置的步骤

    访问用户设置并为其分配新值,如下例所示:

    Properties.Settings.Default.myColor = Color.AliceBlue;

    如果要保持在应用程序会话之间对用户设置所做的更改,请调用 Save 方法,如以下代码所示:

    Properties.Settings.Default.Save();

    四、交替使用多组设置

    在某些情况下,可能需要在应用程序中使用多组设置。例如,如果正在开发的应用程序中有某组设置预计会频繁进行更改,则比较明智的做法是将其全都分成单个文件,这样便可成批替换相应文件,而不会使其他设置受到影响。Visual Studio 2005 允许向项目中添加多组设置。可以通过各自节点中生成的设置对象来访问各组附加设置。例如,如果向项目中添加了名为 SpecialSettings 的一组设置,则要通过 Properties.SpecialSettings 对象来访问该组设置包含在代码中的设置。

    添加附加设置组的步骤

    • 从“Project”(项目)菜单中选择“Add New Item”(添加新项)。将会打开“Add New Item”(添加新项)对话框。
    • 在“Add New Item”(添加新项)对话框中,选择“Settings File”(设置文件)。
    • 在“Name”(名称)框中为设置文件命名,如 SpecialSettings.settings,然后单击“Add”(添加),将文件添加到解决方案中。
    • 在“Solution Explorer”(解决方案资源管理器)中,将新的设置文件拖入到 Properties 文件夹中。这样便可在代码中使用新的设置。
    • 如在其他任何设置文件中那样在此文件中添加和使用设置。可通过 Properties.SpecialSettings 对象访问此组设置。

    五、结束语

    在本文中,您已了解如何在 C# 应用程序中使用设置来存储和管理从属于应用程序和用户的设置。您还学会了如何在设计时添加设置、如何在运行时读写设置,以及如何将多组附加设置合并到应用程序中。

     
c
撰写新文章
j
下一篇
k
上一篇
r
回复
e
编辑
o
显示/隐藏评论
t
返回顶部
esc
关闭