[转载].net framework 3.5 和 4.0 的结构图以及Namespaces参考

mikel阅读(934)

[转载].net framework 3.5 和 4.0 的结构图以及Namespaces参考,强烈推荐下载了解! – 肖品 – 博客园.

经常会被别人问你熟悉.NET Framework吗?  而且还不清楚.NET Framework 到底有哪些东西,然后我就在网上找了一些资料,供自己收集总结,同时也将这份资料分享给各位博友,共同学习,共同进步!

其他相关学习资料:

MSDB库(中文)

ASP.NET 4

.NET Framework 3.5 结构图下载地址:

http://download.microsoft.com/download/4/a/3/4a3c7c55-84ab-4588-84a4-f96424a7d82d/NET35_Namespaces_Poster_LORES.pdf

http://www.microsoft.com/downloads/en/details.aspx?familyid=7b645f3a-6d22-4548-a0d8-c2a27e1917f8&displaylang=en

效果图如下:

.NET Framework 4.0 结构图下载地址:

http://download.microsoft.com/download/E/6/A/E6A8A715-7695-493C-8CFA-8E0C23A4BE1D/098-115952-NETFX4-Poster.pdf

效果图如下:

如转载,请注明出处,珍惜他人的劳动成果! 谢谢!

[转载]通过源代码研究ASP.NET MVC(七)

mikel阅读(1038)

[转载]通过源代码研究ASP.NET MVC(七) – namespace Ivony – 博客园.

通过源代码研究ASP.NET MVC中的Controller和View(一)

通过源代码研究ASP.NET MVC中的Controller和View(二)

通过源代码研究ASP.NET MVC中的Controller和View(三)

通过源代码研究ASP.NET MVC中的Controller和View(四)

通过源代码研究ASP.NET MVC中的Controller和View(五)

通过源代码研究ASP.NET MVC中的Controller和View(六)

呃,细心的朋友可能已经发现了,到这里这个系列开始改名了,这是因为,从这一篇开始,我不得不来研究ASP.NET MVC中的Model模型了。因为我发现如果要Jumony视图引擎要充分利用ASP.NET MVC的功能的话,Model的支持是绕不过去的。

先来温习一下之前得到的结论,过了这么久,您忘了没?

Controller将执行Action的操作外包给了ControllerActionInvoker,其工作流程大体上是这样的:

  • 查找Action(FindAction)
  • 获取参数
  • InvokeActionMethod
  • InvokeActionResult

那么在上一篇,我们已经完整的了解了查找Action的过程,按照传统,这一篇应该对查找Action的过程进行一个总结,但现在需要打破这个传统了,因为这个总结已经有人写过了:

深入理解ASP.NET MVC(7)

别人的东西可以拿来用的,一定要坚定的拿来用,因为软件领域的轮子已经堆积成山了——懒人原则

FindAction步骤找到的Action会经由授权筛选器处理,然后在真正调用Action之前,有一个关键步骤,那就是获取参数:

            IDictionary<string, object> parameters = GetParameterValues( controllerContext, actionDescriptor );
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }别看这个步骤只是简单的调用了一个方法,事实上MVC中Model部分的逻辑大半都集中在这个方法的调用中。

    protected virtual IDictionary<string, object> GetParameterValues( ControllerContext controllerContext, ActionDescriptor actionDescriptor )
    {
      Dictionary<string, object> parametersDict = new Dictionary<string, object>( StringComparer.OrdinalIgnoreCase );
      ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters();
 
      foreach ( ParameterDescriptor parameterDescriptor in parameterDescriptors )
      {
        parametersDict[parameterDescriptor.ParameterName] = GetParameterValue( controllerContext, parameterDescriptor );
      }
      return parametersDict;
    }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }首先是创建了一个Dictionary用来承载返回结果,然后通过actionDescriptor的GetParameters方法获取所有参数的参数描述符,下面是一个枚举,对每一个参数调用GetParameterValue方法来获取具体的值,最后返回。

从前面的研究可知,这里的actionDescriptor的实例类型显然是ReflectedActionDescriptor,其GetParameters方法实现如下:

    public override ParameterDescriptor[] GetParameters()
    {
      ParameterDescriptor[] parameters = LazilyFetchParametersCollection();
 
      // need to clone array so that user modifications aren't accidentally stored
      return (ParameterDescriptor[]) parameters.Clone();
    }
 
    private ParameterDescriptor[] LazilyFetchParametersCollection()
    {
      return DescriptorUtil.LazilyFetchOrCreateDescriptors<ParameterInfo, ParameterDescriptor>(
          ref _parametersCache /* cacheLocation */,
          MethodInfo.GetParameters /* initializer */,
          parameterInfo => new ReflectedParameterDescriptor( parameterInfo, this ) /* converter */);
    }

这里使用了不同于获取ControllerDescriptor的缓存体系。但从这个方法冗长的名字也能一目了然他是干什么的,或者说这里的代码去除缓存后大体上就等同于:

    public override ParameterDescriptor[] GetParameters()
    {
      ParameterDescriptor[] parameters = Array.ConvertAll( MethodInfo.GetParameters(), parameterInfo => new ReflectedParameterDescriptor( parameterInfo, this ) );
 
      // need to clone array so that user modifications aren't accidentally stored
      return (ParameterDescriptor[]) parameters.Clone();
    }

注释说明了为什么要进行Clone,这样使得用户会因为意外情况而的修改缓存的母本。因为数组本质上来说不是一个安全的容器,其大小不可变,但每一 个元素都可以通过简单的赋值而修改。不过Jumony在解决这种问题时,使用的是只读的容器,而非每次访问都创建副本。MVC的各个部分显然是由不同的团 队完成的,从命名的风格和代码习惯上就能很明显的看出来。从源代码入手研究的确是一件非常困难的事情,但好处是你可以得到很多研究结论之外的收获。

最后还是来瞄一眼证实一下猜测:

  internal static class DescriptorUtil
  {
 
    public static TDescriptor[] LazilyFetchOrCreateDescriptors<TReflection, TDescriptor>( 
      ref TDescriptor[] cacheLocation, Func<TReflection[]> initializer, Func<TReflection, TDescriptor> converter )
    {
      // did we already calculate this once?
      TDescriptor[] existingCache = Interlocked.CompareExchange( ref cacheLocation, null, null );
      if ( existingCache != null )
      {
        return existingCache;
      }
 
      TReflection[] memberInfos = initializer();
      TDescriptor[] descriptors = memberInfos.Select( converter ).Where( descriptor => descriptor != null ).ToArray();
      TDescriptor[] updatedCache = Interlocked.CompareExchange( ref cacheLocation, descriptors, null );
      return updatedCache ?? descriptors;
    }
 
  }

又见到了这个CompareExchange,这是针对引用类型的一个重载。第一个CompareExchange的逻辑是,如果 cacheLocation是null,那么将null赋给它,但无论有没有被赋值,会返回cacheLocation在赋值前的引用,同时以上操作都是 原子操作。

其实这段代码有点莫名其妙,我猜测CompareExchange的交换赋值是个幌子,原子操作取值是真,这是为了避免在取值的时候,恰好又被赋值而造成冲突,但事实上我看不到第一个CompareExchange的必要性。

那么第一个ExchangeCompare可以简单的理解为:

      TDescriptor[] existingCache = cacheLocation;

然后判断这个cacheLocation不为null,那么就直接返回。否则进行创建。

创建descriptors的过程与上面的猜测一样,只是多了一个排除空值,接下来是第二个CompareExchange,这个的目的就非常明确 和有必要了,因为在创建descriptors过程中,在其他线程可能已经创建完毕了,这时候则抛弃创建出来的descriptor的值。因为第二个 CompareExchange的逻辑是,如果cacheLocation仍然是null,那么将descriptors赋给它。但下面的 updatedCache ?? descriptors又有点无厘头,因为似乎直接return cacheLocation就可以了。

瞄一眼完毕,那么猜测又一次命中。这样,创建ParameterDescriptor的逻辑就是new ReflectedParameterDescriptor( parameterInfo, this ),其中的parameterInfo就是MethodInfo.GetParameter()的结果,也就是Action方法的每一个参数的信息。

继续回到GetParameterValues方法,在获取ParameterDescriptor后,会针对每一个ParameterDescriptor调用GetParameterValue方法来获取具体的值:

      foreach ( ParameterDescriptor parameterDescriptor in parameterDescriptors ) 
      { 
        parametersDict[parameterDescriptor.ParameterName] = GetParameterValue( controllerContext, parameterDescriptor ); 
      }

那么来看看GetParameterValue方法:

    protected virtual object GetParameterValue( ControllerContext controllerContext, ParameterDescriptor parameterDescriptor )
    {
      // collect all of the necessary binding properties
      Type parameterType = parameterDescriptor.ParameterType;
      IModelBinder binder = GetModelBinder( parameterDescriptor );
      IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
      string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
      Predicate<string> propertyFilter = GetPropertyFilter( parameterDescriptor );
 
      // finally, call into the binder
      ModelBindingContext bindingContext = new ModelBindingContext()
      {
        FallbackToEmptyPrefix = ( parameterDescriptor.BindingInfo.Prefix == null ), // only fall back if prefix not specified
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType( null, parameterType ),
        ModelName = parameterName,
        ModelState = controllerContext.Controller.ViewData.ModelState,
        PropertyFilter = propertyFilter,
        ValueProvider = valueProvider
      };
 
      object result = binder.BindModel( controllerContext, bindingContext );
      return result ?? parameterDescriptor.DefaultValue;
    }

注释写的很明白,首先是搜集所有必须的绑定属性,先是parameterType,它等于参数的类型,因为ReflectionParameterDescriptor的实现是这样的:

    public override Type ParameterType
    {
      get
      {
        return ParameterInfo.ParameterType;
      }
    }

然后是binder,是GetModelBinder方法的结果,我们一会儿再来看这个方法,再接下来是valueProvider,其值是 Controller的ValueProvider属性。然后是parameterName,这个值先要检查 parameterDescriptor.BindingInfo.Prefix,如果不为空,就使用ParameterName。当 然,ParameterName的实现显然是直接把参数名返回。这个BindingInfo我也回头再来看。接下来是propertyFilter,这是 一个Predicate<T>委托类型的,这个委托的定义就是接收一个T参数,返回一个布尔值。结合名称来看,这个委托应该是提供决定哪些属 性要被绑定的依据。

接下来是创建ModelBindingContext,ModelMetadata则是通过GetMetadataForType方法获得。 FallbackToEmptyPrefix则是判断Prefix属性是不是null,ModelState直接获取ViewData中的 ModelState。其他的属性则是使用上面定义的的变量值。

最后,调用IModelBinder的BindModel方法,如果返回的值是null,则使用parameterDescriptor.DefaultValue。

坦白说,对这段代码的第一印象就是乱。首先是上面定义的变量,几乎都是用于创建ModelBindingContext的,这本来没什么问题,但在 创建ModelBindingContext实例的时候,又夹杂了一些初始化代码而不是简单的直接用上面定义的变量来赋值。或者说同一个行为,使用了两种 方式,这不是一个良好的代码习惯。其次,这里的ValueProvider使用了Controller中的属性值,而ModelState更是把手伸到了 ViewData,这使得Model这里与Controller和View都建立了强耦合,这不得不说也是一个糟糕的设计。

事实上,Model部分的代码质量明显要比其他部分的代码质量要糟糕,这一点在后面的探索中,您就会看到。

将代码改成如下形式,是不是更清晰:

    protected virtual object GetParameterValue( ControllerContext controllerContext, ParameterDescriptor parameterDescriptor )
    {
 
      IModelBinder binder = GetModelBinder( parameterDescriptor );
 
      ModelBindingContext bindingContext = new ModelBindingContext()
      {
        ModelName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName,
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType( null, parameterDescriptor.ParameterType ),
        ModelState = controllerContext.Controller.ViewData.ModelState,
 
        FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
        PropertyFilter = GetPropertyFilter( parameterDescriptor ),
        ValueProvider = controllerContext.Controller.ValueProvider
 
      };
 
      object result = binder.BindModel( controllerContext, bindingContext );
 
      return result ?? parameterDescriptor.DefaultValue;
    }

至少,它凸显了主线逻辑,即:

  • 获取Binder
  • 构造BindingContext
  • 绑定Model

那么,我们来看看获取Binder的逻辑是怎样的:

    private IModelBinder GetModelBinder( ParameterDescriptor parameterDescriptor )
    {
      // look on the parameter itself, then look in the global table
      return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder( parameterDescriptor.ParameterType );
    }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }首先尝试从参数的BindingInfo中来获取Binder,如果没有,那么就从Binders中来获取。

BindingInfo今天已经出场N次了,这东西哪来的?它是ParameterDescriptor的一个属性,在ReflectionParameterDescriptor构造函数中创建了其实例:

    public ReflectedParameterDescriptor( ParameterInfo parameterInfo, ActionDescriptor actionDescriptor )
    {
      if ( parameterInfo == null )
      {
        throw new ArgumentNullException( "parameterInfo" );
      }
      if ( actionDescriptor == null )
      {
        throw new ArgumentNullException( "actionDescriptor" );
      }
 
      ParameterInfo = parameterInfo;
      _actionDescriptor = actionDescriptor;
      _bindingInfo = new ReflectedParameterBindingInfo( parameterInfo );
    }
  
    public override ParameterBindingInfo BindingInfo
    {
      get
      {
        return _bindingInfo;
      }
    }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }


我们继续看ReflectedParameetrBindingInfo的代码,看看Prefix和Binder属性都是怎么来的:

    public ReflectedParameterBindingInfo( ParameterInfo parameterInfo )
    {
      _parameterInfo = parameterInfo;
      ReadSettingsFromBindAttribute();
    }

其构造函数很简单,设置了parameterInfo后,就调用了ReadSettingsFormBindAttribute:

    private void ReadSettingsFromBindAttribute()
    {
      BindAttribute attr = (BindAttribute) Attribute.GetCustomAttribute( _parameterInfo, typeof( BindAttribute ) );
      if ( attr == null )
      {
        return;
      }
 
      _exclude = new ReadOnlyCollection<string>( AuthorizeAttribute.SplitString( attr.Exclude ) );
      _include = new ReadOnlyCollection<string>( AuthorizeAttribute.SplitString( attr.Include ) );
      _prefix = attr.Prefix;
    }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas,”Courier New”,courier,monospace; background-color: rgb(255, 255, 255); }.csharpcode pre { margin: 0em; }.csharpcode .rem { color: rgb(0, 128, 0); }.csharpcode .kwrd { color: rgb(0, 0, 255); }.csharpcode .str { color: rgb(0, 96, 128); }.csharpcode .op { color: rgb(0, 0, 192); }.csharpcode .preproc { color: rgb(204, 102, 51); }.csharpcode .asp { background-color: rgb(255, 255, 0); }.csharpcode .html { color: rgb(128, 0, 0); }.csharpcode .attr { color: rgb(255, 0, 0); }.csharpcode .alt { background-color: rgb(244, 244, 244); width: 100%; margin: 0em; }.csharpcode .lnum { color: rgb(96, 96, 96); }ReadSettingsFromBindAttribute方法正如其名,从附加在参数上的BindAttribute特性中获取配置值。并设置自己的属性值。可是设置的值里面,还是不包括Binder属性。那么来看看Binder属性的实现:

    public override IModelBinder Binder
    {
      get
      {
        IModelBinder binder = ModelBinders.GetBinderFromAttributes( _parameterInfo,
            () => String.Format( CultureInfo.CurrentUICulture, MvcResources.ReflectedParameterBindingInfo_MultipleConverterAttributes,
                _parameterInfo.Name, _parameterInfo.Member ) );
 
        return binder;
      }
    }

继续看GetBinderFormAttributes方法的实现,尽管从方法名已经可以知道他大概是干什么的了:

    internal static IModelBinder GetBinderFromAttributes( ICustomAttributeProvider element, Func<string> errorMessageAccessor )
    {
      CustomModelBinderAttribute[] attrs = (CustomModelBinderAttribute[]) element.GetCustomAttributes( typeof( CustomModelBinderAttribute ), true /* inherit */);
      return GetBinderFromAttributesImpl( attrs, errorMessageAccessor );
    }

获取附加的CustomModelBinderAttribute特性然后继续调用GetBinderFromAttributesImpl方法:

    private static IModelBinder GetBinderFromAttributesImpl( CustomModelBinderAttribute[] attrs, Func<string> errorMessageAccessor )
    {
      // this method is used to get a custom binder based on the attributes of the element passed to it.
      // it will return null if a binder cannot be detected based on the attributes alone.
 
      if ( attrs == null )
      {
        return null;
      }
 
      switch ( attrs.Length )
      {
        case 0:
          return null;
 
        case 1:
          IModelBinder binder = attrs[0].GetBinder();
          return binder;
 
        default:
          string errorMessage = errorMessageAccessor();
          throw new InvalidOperationException( errorMessage );
      }
    }

逻辑很简单,如果恰好只有一个特性,那么调用特性的GetBinder返回,如果没有一个,返回null,其他情况,使用errorMessageAccessor获取异常描述,抛出异常。

个人感觉这里的代码风格有着浓重的外包风格,冗长的调用链,甚少意义的参数以及明显生硬的抽出公共代码的痕迹。

简而言之,ReflectedParameterBindingInfo的Binder属性尝试利用附加在参数上的BinderAttribute特性的GetBinder方法来获取Binder。如果没有通过BinderAttribute来获取到Binder,那么:

?? Binders.GetBinder( parameterDescriptor.ParameterType );

Binders是一个ControllerActionInvoker的一个属性:

    protected internal ModelBinderDictionary Binders
    {
      get
      {
        if ( _binders == null )
        {
          _binders = ModelBinders.Binders;
        }
        return _binders;
      }
      set
      {
        _binders = value;
      }
    }

其默认值是ModelBinders.Binders。ModelBinders.Binders则是系统中所有注册的IModelBinder的集合,来看看GetBinder的实现:

    public IModelBinder GetBinder( Type modelType )
    {
      return GetBinder( modelType, true /* fallbackToDefault */);
    }
 
    public virtual IModelBinder GetBinder( Type modelType, bool fallbackToDefault )
    {
      if ( modelType == null )
      {
        throw new ArgumentNullException( "modelType" );
      }
 
      return GetBinder( modelType, ( fallbackToDefault ) ? DefaultBinder : null );
    }
 
    private IModelBinder GetBinder( Type modelType, IModelBinder fallbackBinder )
    {
      // Try to look up a binder for this type. We use this order of precedence:
      // 1. Binder registered in the global table
      // 2. Binder attribute defined on the type
      // 3. Supplied fallback binder
 
      IModelBinder binder;
      if ( _innerDictionary.TryGetValue( modelType, out binder ) )
      {
        return binder;
      }
 
      binder = ModelBinders.GetBinderFromAttributes( modelType,
          () => String.Format( CultureInfo.CurrentUICulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName ) );
 
      return binder ?? fallbackBinder;
    }

这里首先调用的是第一个重载,也就是只有一个参数的,然后他会调用到第二个重载,fallbackToDefault参数为true,再然后,会调 用到第三个重载,因为fallbackToDefault参数是true,所以fallbackBinder参数的值会是DefaultBinder。

第三个重载才是真正干活的方法,首先尝试从_innerDictionary中获取IModelBinder实例,以modelType为键(这里 的modelType就是parameterDescriptor.ParameterType,也就是参数的类型)。如果获存在,那么将其返回,否则, 调用ModelBinders.GetBinderFromAttributes方法,这个方法刚刚才看过。其作用是从附加在modelType的 BinderAttribute特性上获取Binder的实例。如果这也没有获取到,那么就返回fallbackBinder(也就是 DefaultBinder)。

那么我们可以发现IModelBinder的选择过程和原则:

  • 尝试从附加在参数的BinderAttribute特性获取
  • 尝试从ModelBinders.Binders集合中按参数类型检索
  • 尝试从附加在参数的类型的BinderAttribute特性获取
  • 使用ModelBinders.Binders.DefaultBinder

由上自下,优先级递减,在任何一个步骤找到了合适的Binder,则立即返回。

[转载]东拼西凑的第一个Android应用程序

mikel阅读(1051)

[转载]东拼西凑的第一个Android应用程序 – 地球人 – 博客园.

听说下一个互联网时代是终端时代,而且最近微博、IPhone、Android都特火。于是就开始捣鼓起Android来,虽然去年三月份的时候就 花了三千四百大洋买Milestone。但是一直忙于学习与考试没有功夫来学习学习Android方面的开发。新年元旦利用两天假期+万能的Google 搜出来的代码搞了第一个Android应用程序,虽然是第一个,但不是“Hello World”级别的,至少还是多线程的:)

庐山真面目:MyWhois

image

这就是我的第一个Android应用:MyWhois,通过在输入框输入域名后查询该域名的所有者信息。当然域名Whois信息的查询接口我已经在 我的网站上开发好了,MyWhois只不过是通过http get方式调用一下而以。好了下面就简单介绍一下如何开发这个应用吧。

Android开发环境的搭建

我的环境是Windwos 7 64bit+Eclipse Helios Service Release 1+JDK 6 64bit,那么首先上Android Developer 官网下载最新的installer_r08-windows.exe,下载完之后通过提示安装相应的SDK及工具包,这一步安装还是比较顺利只不过下载的时间较长而以。安装好SDk之后,还需要从官网下载Eclipse的Android开发工具插件ADT并 通过Eclipse菜单中的Help->Install New Software…进行安装,安装好之后在菜单Windows->Preferences->Android->Android Perferences的SDK Location中指向Android SDK的安装目录(如下图)。之前我在安装过程中到这一步之后就一直提示我目录错误,SDK列表刷不出来。其实主要是因为adb.exe与Android 1.5的位置不同,还有我之前安装的ADT版本好像只支持Android 1.5版的。通过升级了ADT就解决了该问题。

image

OK,以上就是Android开发的环境搭建的几个基本步骤。详细的步骤请参考SDK里的Installing the SDK。

MyWhois开发

1.项目目录结构

下面就是具体的应用开发了,在下面的内容开始之前,先允许你开个小差在Eclipse上捣鼓个“Hello World”的Android程序先过一过瘾。那么我们就认为你已经有了一点点的经验。在开发应用程序之前我们首先需要创建一个Android应用程序。 创建好之后我们来看看我们项目的目录结构(如下图)。简单介绍一下:res/layout/main.xml是程序的界面代码部分,src /CBCYE.MyWhois/MyWhois.java是程序的代码部分。

image

2. 程序界面部分开发

习惯了.NET拖拽的方式来开发程序界面而且鉴于IDE严重比不上Visual Studio所以可以使用第三方的DroidDraw来开发应用程序的界面。之后再把代码拷贝到main.xml中。本程序中使用到的控件有:EditText、TextView、Button。具体的界面自己画吧,效果图如下:

image

需要注意的是由于有些Whois信息比较多一屏可能显示不了,那么就需要滚动条。Android里面需要使用ScrollView与TextView结合来实现滚动条功能。具体代码如下:

1 <ScrollView xmlns:android=”http://schemas.android.com/apk/res/android”
2 android:layout_width=”306px” android:layout_height=”410px”
3 android:layout_x=”7px” android:layout_y=”61px”
4 android:scrollbars=”vertical” android:fadingEdge=”vertical”>
5 <TextView
6 android:id=”@+id/ResultView”
7 android:layout_width=”fill_parent”
8 android:layout_height=”fill_parent”
9 android:layout_marginTop=”2px”
10 android:layout_marginBottom=”2px”
11 android:layout_marginLeft=”2px”
12 android:layout_marginRight=”2px”
13 android:text=”请在输入框中输入域名”
14 android:layout_x=”7px”
15 android:layout_y=”61px”
16 >
17 </TextView>
18 </ScrollView>
19

3.程序代码部分开发

本部分基本上靠Google来完成。我自己加了些注释,大家应该看得懂,思路是:通过监听Button的OnClick事件之后获得 EditText的值。创建一个新的线程(否则主界面会假死)向事先定义好的接口地址请求Whois信息并通过Handler来将返回的消息界面到 TextView控件。

MyWhois_SourceCode

package CBCYE.MyWhois;

import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.Button;

import android.os.Message;
import android.os.Handler;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import org.apache.http.util.ByteArrayBuffer;
import org.apache.http.util.EncodingUtils;

public class MyWhois extends Activity {
private TextView vResult;
private EditText eKeyWord;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Button sButton1 = (Button)findViewById(R.id.Submit);
vResult
= (TextView)findViewById(R.id.ResultView);
eKeyWord
= (EditText)findViewById(R.id.KeyWord);

//注册点击事件监听者
sButton1.setOnClickListener(new OnClickListener()
{
public void onClick(View aView) {
TextView vResult
= (TextView)findViewById(R.id.ResultView);
vResult.setText(
正在查询中,请稍等!);

//新建查询线程,否则UI界面会假死
Thread mThread = new Thread(new Runnable() {
public void run() {
try {
//返回的数据需要使用’\n’来换行!
URL url = new URL(http://www.cbcye.cn/whois.axd?w=+eKeyWord.getText().toString());

URLConnection urlConnection = url.openConnection();
InputStream is
= urlConnection.getInputStream();

/* 用ByteArrayBuffer做缓存 */
ByteArrayBuffer baf
= new ByteArrayBuffer(50);
int current = 0;

while((current = is.read()) != 1){
baf.append((
byte)current);
}

/* 将缓存的内容转化为String, 用UTF-8编码 */
String myString
= EncodingUtils.getString(baf.toByteArray(), UTF-8);
//myString = new String(baf.toByteArray());
//vResult.setText(myString);
Message mg = Message.obtain();
mg.obj
= myString;
mHandler.sendMessage(mg);

} catch (Exception e) {
e.printStackTrace();
}
}

});
mThread.start();
}
}

);
}

//定义一个Handler
private Handler mHandler = new Handler(){
public void handleMessage(Message msg){
String m
= (String) msg.obj;
if(m.length()<=0)
{
vResult.setText(
查询结果为空.);
}
else
{
vResult.setText(m);
}
}
};
}

由于程序需要使用网络连接所以需要事先声明程序的权限,这一点还是挺好的,毕竟移动流量比有线的贵很多。设置的地方是在MyWhois.Manifest中添加相应的权限定义语句,具体如下:

MyWhois_Permission

<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package
=”CBCYE.MyWhois”
android:versionCode
=”1″
android:versionName
=”1.0″>
<uses-permission android:name=”android.permission.INTERNET” />
<application android:icon=”@drawable/icon” android:label=”@string/app_name”>
<activity android:name=”.MyWhois”
android:label
=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

</application>

</manifest>

调试与部署

Android应用程序的调试可以使用SDK自带的虚拟机来实现也可以通过USB接口连接真实的手机来实现(需要在手机中开启USB调试)。如果需 要使用虚拟机来实现的话在Eclipse菜单Windows->Android SDK and Manager的Virtual devices中新建虚拟机。

image
那么在调试的时候就会让你选择是使用哪台虚拟机或者是使用USB相连的手机来进行调试,如果选择自动模式的话可能系统会自动启动一台虚拟机来进行调试。

image

最后,我们就可以在虚拟机里看到我们开发的程序了。

image

当然,我们实际开发过程中可能这些信息还不够,那么我们就可以点击Eclipse中的Debug Tab在Log Cat中查看更详细调试信息。USB连接手机的调试与虚拟机类似。

总结

初步试了一下Android,虽然代码都是从网上东拼西凑的而且对Java也不是太了解。但是觉得蛮有意思、蛮有成就感的,特别是自己开发的程序能 在自己的手机上运行时。我觉得程序开发这一些应该问题不大。另外要是界面开发这一块再有点大改进或者有更好的IDE来支持开发的话那就太完美了。未来的互 联网就是终端的天下,信息基于终端开发的应用会越来越多,开发起来也会越来越简单。

源码下载

参考资料

Android之旅–Handler与多线程

Android线程交互(Handler+Thread 和 AsyncTask)

[转载]ASP.NET MVC中的扩展点(六)ActionResult

mikel阅读(1287)

[转载]MVC中的扩展点(六)ActionResult – xfrog – 博客园.

ActionResult是控制器方法执行后返回的结果类型,控制器方法可以返回一个直接或间接从ActionResult抽象类继承的类型,如果返回的 是非ActionResult类型,控制器将会将结果转换为一个ContentResult类型。默认的ControllerActionInvoker 调用ActionResult.ExecuteResult方法生成应答结果。

MVC中实现的默认ActionResult如下:

ActionResult

1、ContentResult: 返回简单的纯文本内容,可通过ContentType属性指定应答文档类型,通过ContentEncoding属性指定应答文档的字符编码。可通过 Controller类中的Content方法便捷地返回ContentResult对象。如果控制器方法返回非ActionResult对象,MVC将 简单地以返回对象的ToString()内容为基础产生一个ContentResult对象。
2、EmptyResult: 返回一个空的结果。如果控制器方法返回一个null,MVC将其转换成EmptyResult对象。
3、RedirectResult: 表示一个连接跳转,相当于ASP.NET中的Response.Redirect方法。对应的Controller方法为Redirect。
4、RedirectToRouteResult: 同样表示一个调转,MVC会根据我们指定的路由名称或路由信息(RouteValueDictionary)来生成Url地址,然后调用 Response.Redirect跳转。对应的Controller方法为RedirectToAction和RedirectToRoute。
5、ViewResult: 表示一个视图结果,它根据视图模板产生应答内容。对应Controller方法为View。
6、PartialViewResult: 表示一个部分视图结果,与ViewResult本质上一致,只是部分视图不支持母版,对应于ASP.NET,ViewResult相当于一个Page,而 PartialViewResult则相当于一个UserControl。它对应的Controller方法为PartialView。
7、HttpUnauthorizedResult: 表示一个未经授权访问的错误。MVC会向客户端发送一个401的应答状态。如果在web.config中开启了表单验证(authentication mode=”Forms”),则401状态会将Url转向指定的loginUrl链接。
8、JavaScriptResult: 本质上是一个文本内容,只是将Response.ContentType设置为 application/x-JavaScript,此结果应该和MicrosoftMvcAjax.js脚本配合使用,客户端接收到Ajax应答后,将 判断Response.ContentType的值,如果是application/x-JavaScript,则直接eval执行返回的应答内容。此结 果类型对应的Controller方法为JavaScript。
9、JsonResult: 表示一个JSON结果。MVC将Response.ContentType设置为application/json,并通过 JavaScriptSerializer类将指定对象序列化为Json表示方式。需要注意,默认情况下,MVC不允许GET请求返回JSON结果,要解 除此限制,在生成JsonResult对象时,将其JsonRequestBehavior属性设置为 JsonRequestBehavior.AllowGet。此结果对应的Controller方法为Json。
10、FilePathResult、FileContentResult、FileStreamResult: 这三个类继承于FileResult,表示一个文件内容,三者的区别在于,FilePath通过路径传送文件到客户端,FileContent通过二进制 数据的方式,而FileStream是通过Stream的方式来传送。Controller为这三个文件结果类型提供了一个名为File的重载方法。

通过直接或间接地从ActionResult继承,可实现自定义的结果类型,下例将实现一个XmlResult类型,用于返回XML应答内容:

1、创建一个空的MVC项目

2、实现XmlResult类

显示行号 复制代码 XmlResult
  1. public class XmlResult : ActionResult
    
  2.  {
    
  3.     public XmlResult(Object data)
    
  4.     {
    
  5.         this.Data = data;
    
  6.     }
    
  7.     public Object Data
    
  8.     {
    
  9.         get;
    
  10.         set;
    
  11.     }
    
  12.     public override void ExecuteResult(ControllerContext context)
    
  13.     {
    
  14.         if (Data == null)
    
  15.         {
    
  16.             new EmptyResult().ExecuteResult(context);
    
  17.             return;
    
  18.         }
    
  19.         context.HttpContext.Response.ContentType = "application/xml";
    
  20.         using (MemoryStream ms = new MemoryStream())
    
  21.         {
    
  22.             XmlSerializer xs = new XmlSerializer(Data.GetType());
    
  23.             xs.Serialize(ms, Data);
    
  24.             ms.Position = 0;
    
  25.             using (StreamReader sr = new StreamReader(ms))
    
  26.             {
    
  27.                 context.HttpContext.Response.Output.Write(sr.ReadToEnd());
    
  28.             }
    
  29.         }
    
  30.     }
    
  31. }
    

.src_container { background-color: rgb(231, 229, 220); width: 99%; overflow: hidden; margin: 12px 0pt ! important; padding: 0px 3px 3px 0px; }.src_container .titlebar { background-color: rgb(212, 223, 255); border-width: 1px 1px 0pt; border-style: solid solid none; border-color: rgb(79, 129, 189) rgb(79, 129, 189) -moz-use-text-color; padding: 3px 24px; margin: 0pt; width: auto; line-height: 120%; overflow: hidden; text-align: left; font-size: 12px; }.src_container .toolbar { display: inline; font-weight: normal; font-size: 100%; float: right; color: rgb(0, 0, 255); text-align: left; overflow: hidden; }.toolbar span.button { display: inline; font-weight: normal; font-size: 100%; color: rgb(0, 0, 255); text-align: left; overflow: hidden; cursor: pointer; }.src_container div.clientarea { background-color: white; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; height: auto; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,courier,monospace,serif; }.src_container ol.mainarea { padding: 0pt 0pt 0pt 52px; margin: 0pt; background-color: rgb(247, 247, 255) ! important; }.number_show { padding-left: 52px ! important; list-style: decimal outside none ! important; }.number_show li { list-style: decimal outside none ! important; border-left: 1px dotted rgb(79, 129, 189); }.number_hide { padding-left: 0px ! important; list-style-type: none ! important; }.number_hide li { list-style-type: none ! important; border-left: 0px none; }ol.mainarea li { display: list-item ! important; font-size: 12px ! important; margin: 0pt ! important; line-height: 18px ! important; padding: 0pt 0pt 0pt 0px ! important; background-color: rgb(247, 247, 255) ! important; color: rgb(79, 129, 189); }ol.mainarea li pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap ol.mainarea li pre { white-space: pre-wrap; word-wrap: break-word; }ol.mainarea li pre.alt { background-color: rgb(247, 247, 255) ! important; }3、创建一个HomeController,实现Index方法

public ActionResult Index()
{
    return new XmlResult(new Product()
    {
        ID = "000001",
        Name = "测a试?",
        Description = ""
    });
}

.codearea { color: black; background-color: white; line-height: 18px; border: 1px solid rgb(79, 129, 189); margin: 0pt; width: auto ! important; overflow: auto; text-align: left; font-size: 12px; font-family: “Courier New”,”Consolas”,”Fixedsys”,”BitStream Vera Sans Mono”,courier,monospace,serif; }.codearea pre { color: black; line-height: 18px; padding: 0pt 0pt 0pt 12px ! important; margin: 0em; background-color: rgb(255, 255, 255) ! important; }.linewrap pre { white-space: pre-wrap; word-wrap: break-word; }.codearea pre.alt { background-color: rgb(247, 247, 255) ! important; }.codearea .lnum { color: rgb(79, 129, 189); line-height: 18px; }

源代码下载

[转载]C#数据本地存储方案之SQLite

mikel阅读(1032)

[转载]C#数据本地存储方案之SQLite – luminji’s web – 博客园.

即使是做网络应用,在断线情况下,也需要考虑数据的本地存储。在SQLite出现之前,数据量大的情况下,我们一直使用ACCESS,数据量小,则文件存储。ACCESS不支持事务原子性,在断电情况下(这种情况总是会发生)会导致数据很难恢复。

一:安装

SQLITE,是一款轻型的数据库,是遵守ACID的关联式数据库管理系统。我直接使用的是http://sqlite.phxsoftware.com/(An open source ADO.NET provider for the SQLite database engine)。下载完毕是一个EXE,安装后根目录如下:

clip_image002

Bin下有一个测试工具,可以查看本地运行SQLITE的各项性能指标。

二:新建数据库

安装完毕后,打开visual studio,新建数据连接,可以看到数据源多了一项SQLite。

clip_image004

新建连接,如下图。SQLITE的数据库,保存后是一个文件。

clip_image006

三:数据库维护

可以在VS中方面的维护SQLITE数据,如下图:

clip_image008

可以在VS中使用类似SQL查询分析器的功能,如下图:

clip_image010

四:混合模式

安装完毕,可以直接在项目集的引用中,多了

System.Data.SQLite

System.Data.SQLite.Linq

两个程序集,由于http://sqlite.phxsoftware.com/的System.Data.SQLite是混合模式程序集,是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集。故需要在App.config中配置如下参数。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>

五:SQLiteHelper

最后,提供一个自己写的SQLiteHelper:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Data;
using System.Data.Common;

namespace Com.Luminji.DataService.SQLHelpers
{
    public class SQLiteHelper
    {
        /// <summary>
        /// ConnectionString样例:Datasource=Test.db3;Pooling=true;FailIfMissing=false
        /// </summary>
        public static string ConnectionString { get; set; }

        private static void PrepareCommand(SQLiteCommand cmd, SQLiteConnection conn, string cmdText, params object[] p)
        {
            if (conn.State != ConnectionState.Open)
                conn.Open();
            cmd.Parameters.Clear();
            cmd.Connection = conn;
            cmd.CommandText = cmdText;
            cmd.CommandType = CommandType.Text;
            cmd.CommandTimeout = 30;
            if (p != null)
            {
                foreach (object parm in p)
                    cmd.Parameters.AddWithValue(string.Empty, parm);
            }
        }

        public static DataSet ExecuteQuery(string cmdText, params object[] p)
        {
            using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
            {
                using (SQLiteCommand command = new SQLiteCommand())
                {
                    DataSet ds = new DataSet();
                    PrepareCommand(command, conn, cmdText, p);
                    SQLiteDataAdapter da = new SQLiteDataAdapter(command);
                    da.Fill(ds);
                    return ds;
                }
            }
        }

        public static int ExecuteNonQuery(string cmdText, params object[] p)
        {
            using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
            {
                using (SQLiteCommand command = new SQLiteCommand())
                {
                    PrepareCommand(command, conn, cmdText, p);
                    return command.ExecuteNonQuery();
                }
            }
        }

        public static SQLiteDataReader ExecuteReader(string cmdText, params object[] p)
        {
            using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
            {
                using (SQLiteCommand command = new SQLiteCommand())
                {
                    PrepareCommand(command, conn, cmdText, p);
                    return command.ExecuteReader(CommandBehavior.CloseConnection);
                }
            }
        }

        public static object ExecuteScalar(string cmdText, params object[] p)
        {
            using (SQLiteConnection conn = new SQLiteConnection(ConnectionString))
            {
                using (SQLiteCommand command = new SQLiteCommand())
                {
                    PrepareCommand(command, conn, cmdText, p);
                    return command.ExecuteScalar();
                }
            }
        }

    }
}
六:附注
SQLite官方网站: http://www.sqlite. org/ 时第一眼看到关于SQLite的特性。
1. ACID事务
2. 零配置 – 无需安装和管理配置
3. 储存在单一磁盘文件中的一个完整的数据库
4. 数据库文件可以在不同字节顺序的机器间自由的共享
5. 支持数据库大小至2TB
6. 足够小, 大致3万行C代码, 250K
7. 比一些流行的数据库在大部分普通数据库操作要快
8. 简单, 轻松的API
9. 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定
10. 良好注释的源代码, 并且有着90%以上的测试覆盖率
11. 独立: 没有额外依赖
12. Source完全的Open, 你可以用于任何用途, 包括出售它
13. 支持多种开发语言,C, PHP, Perl, Java, ASP .NET,Python

[转载]基于事件通信的轻量级MVP框架实现,附源码

mikel阅读(1112)

[转载]基于事件通信的轻量级MVP框架实现,附源码 – Loning’s home – 博客园.

在.NET中,对于ASP.NET,有MVC;对于WPF、SILVERLIGHT,有MVVM。然而在桌面开发领域,似乎微软并没有推出什么强 力的框架。但笔者在写程序的时候很不喜欢把代码全部混杂在一个类中。这个问题很容易解决,一种是使用现成的对平台没有依赖性的MVC框架,比如 PureMVC,当然学习一个框架需要一些时间,另一种方法就是自己做一个小框架,恐怕称之为框架都有些太夸大了。

首先需要确定的是这个小框架要实现的功能。MVC虽然经典,但是View层的数据获取需要从Model直接获取,而View的操作行为则是 需要通过Controller来更新Model。也就是说在View与Model通信过程中,Controller负责那些变更状态的事情。然而MVC中 比较严重的问题是View需要引用Model,这就导致了View层对Model的依赖。主动MVC与被动MVC都存在这样的问题。(见Figure 1 主动MVC、Figure 2 被动MVC)而MVP则不存在这样的问题,但是在MVP中Presenter承担了更多的事情。在Presenter中,大致有两种信息,一种是改变 Model状态的控制信息,一种是改变View显示的状态信息。对于特定的策略,如Presenter是主动的询问Model发现变更后通知View,还 是Model通过Observer模式通知Presenter,Presenter再去通知View改变内容这类的事情,则可以具体到每个特定的 Presenter中来实现。(见Figure 3 MVP模式)

Figure 1 主动MVC

Figure 2 被动MVC

Figure 3 MVP模式

MVP具体到.NET中来,在其中使用的Observer模式自然可以用事件实现。笔者认为虽然一个Presenter有可能会存在需要多 个视图以及多个Model的情况,但是大多情况下,一个Presenter仅仅关注一个Model。对于View,情况则比较复杂,通常为了将某一类 Model的信息显示出来,我们会为其专门定制一个View,但是,我们还需要输出许多信息,看上去我们是需要向其他的View发送信息。但是,如果我们 为每一个View都做一个Presenter,一个Model的话,我们只需要在当前Presenter引用那个View所对应的Model,发送相应信 息就好了。所以,笔者的原则就是,不要让更新不属于当前Presenter的View,而是通过该Presenter引用Model实现其他信息的输出。

Figure 4 框架接口

01 public interface IView
02 {
03 void Initialize();
04 }
05 public interface IModel
06 {
07 void Initialize();
08 event EventHandler Initialized;
09 }
10 public interface IPresenter
11 {
12 void SetView(IView view);
13 void SetModel(IModel model);
14 void SetUnityContainer(IUnityContainer unityContainer);
15 void Initialize();
16 string Name { get; }
17 Guid ID { get; }
18 IView GetView();
19 IModel GetModel();
20 }
21 public interface IPresenter<V, M> : IPresenter
22 where V : IView
23 where M : IModel
24 {
25 void SetView(V view);
26 void SetModel(M model);
27 }

以上是笔者设计的接口。Model与View的初始化有时需要一定的时间,我们可以把这些方法封装起来,便于今后控制。然后在 Presenter中提供了一些基本方法,如更改获取View及Model,以及一个初始化方法,该初始化方法的默认实现是会初始化View后初始化 Model。

在设计过程中,笔者认为有大量的Presenter方法是需要重用的,因此就写了一个抽象的基类实现一些方法,在开发过程中,又引入了 IUnityContainer,因此实现了两个版本。BasePresenter不会自动注入View与Model,而Presenter会自动注入。

Figure 5 Presenter基类

在实现BasePresenter的过程中,使用了模板方法将几个最常用的方法定义出来,如在添加Model的时候绑定Model的事件, 移除Model的时候移除绑定Model的事件。还有就是在Model初始化完成的时候,也经常需要Presenter去做一些事情,也定义了出来。

为了便于标识与更友好的显示名称,也加入了相关的属性来标识。

通过一些这样的定义,我们就已经实现了一个简易的MVP框架。下面来说说这个框架怎么用。

这个框架比较好的一点就是比较适合懒人用,因为消息通知都是基于.net的方法或者事件来实现的,所以IDE都能直接认。

我们现在要实现一个累加器,Model的实现很容易。有一个方法来进行累加,有一个属性来提供当前数字,还有一个事件来通知累加的数已经改变了。

1 public interface IHelloMvpModel : IModel
2 {
3 void Plus();
4 int GetNumber { get; }
5 event EventHandler NumberChanged;
6 }

而View层就更简单了,接收用户操作与显示数据。

1 public interface IHelloMvpView : IView
2 {
3 void SetNumber(int number);
4 event EventHandler ChangeNumberRequested;
5 }

在主程序,我们这样来进行调用。

01 static void Main()
02 {
03 Application.EnableVisualStyles();
04 Application.SetCompatibleTextRenderingDefault(false);
05
06 IUnityContainer container = new UnityContainer();
07 container.RegisterInstance(container);
08 container.RegisterType<IHelloMvpModel, HelloMvpModel>();
09 container.RegisterType<IHelloMvpView, HelloMvpView>();
10
11 var p = container.Resolve<HelloMvpPresenter>();
12 p.Initialize();
13 Application.Run((Form) p.View);
14 }

Figure 6 运行截图

Figure 7 点击按钮后

虽然实现起来比较繁杂,但是对比较大的程序来说,有这样一套框架可以很好的帮助开发者,使得混杂在一起的数据更加的清晰。笔者现在已经通过这样的框架实现了一些实用的程序。

这篇文章大概就完了,可能有时间会再写一篇用该框架实现一些通用ToolWindow的实现,在程序源码中的Loning.MvpWinform项 目中。写这个小框架大概一年前写的,现在想来IPresenter似乎没什么意义,大概也就是在初始化的时候多态一下,对View与Model的单一限制 似乎也不好,在开发过程中有时也会感到很麻烦。但是比较可以肯定的是开发的时候,思维会比较清晰。如果大家有时间看这篇文章,欢迎指出不足之处。

程序源码: http://www.wiisio.com/LoningLibrary.7z

参考文章:MVP模式与MVC模式 http://www.uml.org.cn/sjms/201006244.asp 部分图摘自本篇文章

作者:马昊伯

出处:http://loning.cnblogs.com/

[转载]Silverlight C# 游戏开发:L6 3D摄像机

mikel阅读(1065)

[转载]Silverlight C# 游戏开发:L6 3D摄像机 – Silvery Night – 博客园.

听闻Silverlight5将会支持3D的调用甚至控件,非常兴奋,以后Silverlight就可以开发3D游戏了,但是我这个系列的文章才刚 刚进行到一半,看来要加快速度,这篇主要介绍关于摄像机的相关开发,游戏离不开摄像机(Camera),Camera相当于眼睛,有了眼睛才看到世界,可 是眼睛也有相应的参数,在Balder 3D中创建摄像机相当的简单,在前几篇的代码中就能很容易的看出来,但是如何去控制摄像机达到我们的目的呢,先让我们了解一下3D世界的摄像机。

上图是一个3D世界摄像机的基本属性,它和其他的3D物体一致的部分在于空间坐标表示,但是为了看到世界,它需要一些其他的参数来描述功能,比如 Far\Near Clip(近远景裁切)、Target(目标点) 、Perspective field of view(视角)等等,这些属性为我们渲染3D游戏世界提供了基础参照。打开Balder 中Camera类就能看到这些属性,下面是简要说明:

Camera.Far:远景裁切,即最远看到的距离

Camera.Near:近景裁切,即最近看到的距离

Camera.FieldOfView:视角,所看范围,例如人类的视角就是60度

Camera.Target:目标点,即所看的目标位置,两点成一向量嘛

而其他的我们如果不做深层的开发一般用不上,例如Forward、Up、DepthDivisor、DepthZero

下面开发一个小程序来控制摄像机的这些属性,预览图如下:

这个只是一个简单的控件控制属性刷新的小小程序,首先要设计和制作一个界面:

但是这一次,我写了一个Mesh的组来方便管理,就如我们前面提到的一样,即便是复杂的人物模型,也可以通过这样的方式组合起来:

代码

/// <summary>
/// 自定义的模型组,即便是复杂的人物模型,也可以通过这样的方式组合出来
/// </summary>
public class MeshGroup : Balder.Objects.Geometries.Geometry
{
//计时器
DispatcherTimer _dispatchertimer = new DispatcherTimer();
public MeshGroup()
{
Name
= MeshGroup;
Game_Axis axis_x
= new Game_Axis(new Vertex(300, 0, 0), new Vertex(300, 0, 0), Colors.Red);
Game_Axis axis_y
= new Game_Axis(new Vertex(0, 300, 0), new Vertex(0, 300, 0), Colors.Blue);
Game_Axis axis_z
= new Game_Axis(new Vertex(0, 0, 300), new Vertex(0, 0, 300), Colors.Green);
Children.Add(axis_x);
Children.Add(axis_y);
Children.Add(axis_z);
Mesh Teapot
= new Mesh();
Teapot.Position
= new Coordinate(0, 0, 0);
Teapot.AssetName
= new Uri(/Balder_Studio;component/Res/teapot.ase, UriKind.Relative);
Children.Add(Teapot);
Children.Add(_box);
Children.Add(
new Box() { Dimension = new Coordinate(10, 30, 10), Position = new Coordinate(10, 10, 75) });
MainPage.MainThis.ToShow.Click
+= new System.Windows.RoutedEventHandler(ToShow_Click);
_dispatchertimer.Tick
+= new EventHandler(_dispatchertimer_Tick);
_dispatchertimer.Interval
= TimeSpan.FromMilliseconds(30);
}
void ToShow_Click(object sender, System.Windows.RoutedEventArgs e)
{
_dispatchertimer.Start();
}
Balder.Objects.Geometries.Box _box
= new Box() { Dimension = new Coordinate(10, 20, 30), Position = new Coordinate(75, 10, 0), Name = MyBox };
//旋转角度计数
float angle = 0;
float speed = 2;
void _dispatchertimer_Tick(object sender, EventArgs e)
{
World
= Balder.Math.Matrix.CreateRotationY(angle++);
_box.Position.Y
+= speed;
if (_box.Position.Y >= 50 || _box.Position.Y <= 0)
speed
= speed;

}
}

在这个组里面,在内部实现了一个动画,一块砖头在飘忽,动画参见 L4 模型组和简单的动画
我们在Lesson06.xmal.cs文件中加入如下代码:

Lesson06

public partial class Lesson06 : UserControl
{
public Camera _myCamera = new Camera();
public Lesson06()
{
InitializeComponent();

slider_axis_x.ValueChanged += new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_axis_ValueChanged);
slider_axis_y.ValueChanged
+= new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_axis_ValueChanged);
slider_axis_z.ValueChanged
+= new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_axis_ValueChanged);

slider_axis_x1.ValueChanged += new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_Target_ValueChanged);
slider_axis_y1.ValueChanged
+= new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_Target_ValueChanged);
slider_axis_z1.ValueChanged
+= new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_Target_ValueChanged);

slider_perspective.ValueChanged += new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_Target_ValueChanged);
slider_near_clip.ValueChanged
+= new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_Target_ValueChanged);
slider_far_clip.ValueChanged
+= new System.Windows.RoutedPropertyChangedEventHandler<double>(slider_Target_ValueChanged);

//L3
Game game = new Game() { Width = 600, Height = 400 };
game.Camera
= _myCamera;
game.Camera.Position
= new Coordinate(100, 120, 150);
game.Camera.Target
= new Coordinate(0, 0, 0);

game.Children.Add(new OmniLight() { Position = new Coordinate(0, 0, 0) });
game.Children.Add(
new MeshGroup());
LayoutRoot.Children.Add(game);
}

void slider_axis_ValueChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
{
axis_z.Text
= ((int)slider_axis_z.Value).ToString();
_myCamera.Position.Z
= slider_axis_z.Value;
axis_y.Text
= ((int)slider_axis_y.Value).ToString();
_myCamera.Position.Y
= slider_axis_y.Value;
axis_x.Text
= ((int)slider_axis_x.Value).ToString();
_myCamera.Position.X
= slider_axis_x.Value;
}
void slider_Target_ValueChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
{
axis_z1.Text
= ((int)slider_axis_z1.Value).ToString();
axis_y1.Text
= ((int)slider_axis_y1.Value).ToString();
axis_x1.Text
= ((int)slider_axis_x1.Value).ToString();
_myCamera.Target.Z
= slider_axis_z1.Value;
_myCamera.Target.Y
= slider_axis_y1.Value;
_myCamera.Target.X
= slider_axis_x1.Value;

text_perspective.Text = ((int)slider_perspective.Value).ToString();
text_near_clip.Text
= ((int)slider_near_clip.Value).ToString();
text_far_clip.Text
= ((int)slider_far_clip.Value).ToString();
_myCamera.Far
= (float)slider_far_clip.Value;
_myCamera.Near
= (float)slider_near_clip.Value;
_myCamera.FieldOfView
= slider_perspective.Value;

}
}

我尽量简单描写,建议不是太明白的朋友参看之前的文章,主要是用Slider控件控制属性达到效果。

那么最后我们运行一下看看效果吧:

获取 Microsoft Silverlight

源代码下载地址:点击这里下载工程

工程中如果缺少Balder.dll请在这里快速下载:SL4_Balder.rar

推荐Silverlight游戏开发博客:深蓝色右手

[转载]flex 联机游戏开发 - 中国象棋游戏:(一)核心逻辑

mikel阅读(976)

[转载]flex 联机游戏开发 – 中国象棋游戏:(一)核心逻辑 – 博弈居 – 开放的第三方应用游戏开发测试平台 – 博客园.

在开发四国军棋的游戏中,通过 flex联机游戏开发- 四国军棋游戏(五)-提炼棋类开发api,我们提炼出了第一个关于棋类游戏开发的api-FlexChessAPI,这个api设计的方针就是基于状态机 与事件驱动的flex主要机制,使开发工作简洁易行。现在,我们第一次使用这个api来开发一款中国象棋游戏,对一个成熟的开发工作者来说,我相信,你大 概只需要半天时间就可以让这个象棋游戏运作起来。

现在,我们将中国象棋的逻辑出来。

1)中国象棋由9*10个方格,与四国军棋不同,棋子本身并无行棋路线,

也就是说我们只需要简单扩展ipostion 形成posiitonVO类即可

2)中国象棋的棋子本身无大小,但每个子有自己的行棋路线,吃子的方式是目标子正好处于该子的行棋路线上,另外,其它的子的行为会对目标子的行棋路线造成影响。

也就是说,不论是移动,吃子等,我们都无法立即获得该子的可移动路线,只有等对方行完棋后才能确定行棋路线,所以我们选择的计算行棋路线的方式应该定位在 选中源棋子的时候。

下面,我设计一个ChinaChessHelp类,类中的所有方法与变量都是静态类。

2.1 列出所有棋子,(注意,红方的相与黑方的象不但显示名称不同,行棋路线也不同,这样,我就将他们处理成两上不同的棋子),至于棋子的值,并不重要,只是一个区别作用。

//棋子
public static const PIECE_RED_CHE:int=56;
public static const QIZHI_RED_MA:int=57;
public static const QIZHI_RED_XIAN:int=58;
public static const QIZHI_RED_SHI:int=59;
public static const QIZHI_RED_JIAN:int=60;
public static const QIZHI_RED_BING:int=55;
public static const QIZHI_RED_PAO:int=54;

public static const PIECE_BLACK_CHE:int=156;
public static const QIZHI_BLACK_MA:int=157;
public static const QIZHI_BLACK_XIAN:int=158;
public static const QIZHI_BLACK_SHI:int=159;
public static const QIZHI_BLACK_SUAN:int=160;
public static const QIZHI_BLACK_ZHU:int=155;
public static const QIZHI_BLACK_PAO:int=154;

2.2 设计棋子的移动路线,这是中国象棋最核心的逻辑。请参与这个图,我设计出每个子的行棋路线,对于一些行棋路线有限而且透明的棋子,我直接索引出值(如士, 象,师),对一些行棋路线不确定的棋子,我们计算出他的行棋路线,如车,马,炮等。所有的索引请参照下面这个图像(棋盘的绘制我在下一节将讲到)。

2.2.1 函数 initMoveRedJian():ArrayCollection

将的行棋路线 用switch case来描述就是如下

/**
* 紅方將的行棋路線
* @param posindex
* @return
*
*/
public static function initMoveRedJian(posindex:int):ArrayCollection{
var temp:ArrayCollection;
switch(posindex){
case 3:
temp=new ArrayCollection([4,12]);
break;
case 4:
temp=new ArrayCollection([3,5,13]);
break;
case 5:
temp=new ArrayCollection([4,14]);
break;
case 12:
temp=new ArrayCollection([3,13,21]);
break;
case 13:
temp=new ArrayCollection([4,12,14,22]);
break;
case 14:
temp=new ArrayCollection([5,13,23]);
break;
case 21:
temp=new ArrayCollection([12,22]);
break;
case 22:
temp=new ArrayCollection([13,21,23]);
break;
case 23:
temp=new ArrayCollection([14,22]);
break;
default:
break;
}
return temp;
}

2.2.2 函数 initMoveRedShi():ArrayCollection

士的行棋路线 用switch case来描述就是如下

/**
* 紅方士的行棋路線
* @param posindex
* @return
*
*/
public static function initMoveRedShi(posindex:int):ArrayCollection{
var temp:ArrayCollection;
switch(posindex){
case 3:
temp=new ArrayCollection([13]);
break;
case 5:
temp=new ArrayCollection([13]);
break;
case 13:
temp=new ArrayCollection([3,5,21,23]);
break;
case 21:
temp=new ArrayCollection([13]);
break;
case 23:
temp=new ArrayCollection([13]);
break;
default:
break;
}
return temp;
}

逻辑是这样,但上面的写法是错误的,flex中当数组只有一个元素时,最好不要直接用arraycollection进行初始化。您可以修改一下:)

2.2.3 红方象的行棋路线 initMoveRedXian

/**
* 紅方象的行棋路線
* 注意蹩脚路线。
* @param posindex
* @return
*
*/
public static function initMoveRedXian(posindex:int,pieceMap:HashMap):ArrayCollection{
var temp:ArrayCollection=new ArrayCollection();
switch(posindex){
case 2:
if ((pieceMap.get(10) as PieceVO).deskpos==-1)
temp.addItem(18);
if ((pieceMap.get(12) as PieceVO).deskpos==-1)
temp.addItem(22);
break;
case 6:
if ((pieceMap.get(14) as PieceVO).deskpos==-1)
temp.addItem(22);
if ((pieceMap.get(16) as PieceVO).deskpos==-1)
temp.addItem(26);
break;
case 18:
if ((pieceMap.get(10) as PieceVO).deskpos==-1)
temp.addItem(2);
if ((pieceMap.get(28) as PieceVO).deskpos==-1)
temp.addItem(38);
break;
case 22:
if ((pieceMap.get(12) as PieceVO).deskpos==-1)
temp.addItem(2);
if ((pieceMap.get(14) as PieceVO).deskpos==-1)
temp.addItem(6);
if ((pieceMap.get(30) as PieceVO).deskpos==-1)
temp.addItem(38);
if ((pieceMap.get(32) as PieceVO).deskpos==-1)
temp.addItem(42);
break;
case 26:
if ((pieceMap.get(16) as PieceVO).deskpos==-1)
temp.addItem(6);
if ((pieceMap.get(34) as PieceVO).deskpos==-1)
temp.addItem(42);

break;
case 38:
if ((pieceMap.get(28) as PieceVO).deskpos==-1)
temp.addItem(18);
if ((pieceMap.get(30) as PieceVO).deskpos==-1)
temp.addItem(22);
break;
case 42:
if ((pieceMap.get(32) as PieceVO).deskpos==-1)
temp.addItem(22);
if ((pieceMap.get(34) as PieceVO).deskpos==-1)
temp.addItem(26);
break;
default:
break;
}
return temp;
}

2.2.4 红方兵的行棋路线 public static function initMoveRedBing():ArrayCollection

注意的是,并没有过河时,只能向前走,过河后则可以有三个位置可以走。

利用posindex的值进行判断

/**
* 紅方并的行棋路線
* @param posindex
* @return
*
*/
public static function initMoveRedBing(posindex:int,pieceMap:HashMap):ArrayCollection{
var temp:ArrayCollection=new ArrayCollection();
if (posindex%9-1>=0&&posindex>=45) temp.addItem(posindex-1);
if (posindex%9+1<9&&posindex>=45) temp.addItem(posindex+1);
if (posindex+9<90) temp.addItem(posindex+9);
return temp;
}

2.2.5 马的行棋路线 public static function initMoveRedBing():ArrayCollection

车,马,炮属于自由移动棋子,对红黑双方是对等存在,所以可以使用公用函数进行处理。

马的游戏逻辑主要就是判断目标点位是否在棋盘中以及是不是有蹩脚点位存在。

/**
* 马的行棋路線
* 每个马有八个行棋位置,有四个蹩脚点位,
* @param posindex
* @return
*
*/
public static function initMoveMa(posindex:int,pieceMap:HashMap):ArrayCollection{
var temp:ArrayCollection=new ArrayCollection();

//左方向
if (posindex%9-2>=0) {
if ((pieceMap.get(posindex-1) as PieceVO).deskpos==-1)
{
if ((posindex-11)>=0)
{
temp.addItem(posindex-11);
}
if ((posindex+7)<89)
{
temp.addItem(posindex+7);
}

}
}
//右方向
if (posindex%9+2<9) {

if ((pieceMap.get(posindex+1) as PieceVO).deskpos==-1)
{
if ((posindex-7)>=0)
{
temp.addItem(posindex-7);
}
if ((posindex+11)<89)
{
temp.addItem(posindex+11);
}

}
}
//上方向
if (posindex-18>=0) {
if ((pieceMap.get(posindex-9) as PieceVO).deskpos==-1)
{
if ((posindex-19)%9<posindex%9)
{
temp.addItem(posindex-19);
}
if ((posindex-17)%9>posindex%9)
{
temp.addItem(posindex-17);
}

}
}
//下方向
if (posindex+18<90) {
if ((posindex+19)%9>posindex%9)
{
temp.addItem(posindex+19);
}
if ((posindex+17)%9<posindex%9)
{
temp.addItem(posindex+17);
}
}
return temp;
}

2.2.6 车的行棋路线

车的行棋逻辑就是查看四个方向是否有空的位置,如果找到一个棋子,则结束寻找,将当前找到的棋子也加入索引。

/**
* 车的行棋路線
* @param posindex
* @return
*
*/
public static function initMoveChe(posindex:int,pieceMap:HashMap):ArrayCollection{
var temp:ArrayCollection=new ArrayCollection();
//模方向
for (var xr:int=posindex%9+1;xr<9;xr++)
{
if ((pieceMap.get(posindex+xr-posindex%9) as PieceVO).deskpos==-1)
{
temp.addItem(posindex+xr-posindex%9);
}
else
{
//将找到最后一子的位置也加入
temp.addItem(posindex+xr-posindex%9);
break;
}
}
for (var xl:int=posindex%9-1;xl>=0;xl–)
{
if ((pieceMap.get(posindex+xl-posindex%9) as PieceVO).deskpos==-1)
{
temp.addItem(posindex+xl-posindex%9);
}
else
{
temp.addItem(posindex+xl-posindex%9);
break;
}
}
//竖方向
for (var yb:int=Math.floor(posindex/9)+1;yb<10;yb++)
{
if ((pieceMap.get(posindex+9*(yb-Math.floor(posindex/9))) as PieceVO).deskpos==-1)
{
temp.addItem(posindex+9*(yb-Math.floor(posindex/9)));
}
else
{
temp.addItem(posindex+9*(yb-Math.floor(posindex/9)));
break;
}
}
for (var yt:int=Math.floor(posindex/9)-1;yt>=0;yt–)
{
if ((pieceMap.get(posindex+9*(yt-Math.floor(posindex/9))) as PieceVO).deskpos==-1)
{
temp.addItem(posindex+9*(yt-Math.floor(posindex/9)));
}
else
{
temp.addItem(posindex+9*(yt-Math.floor(posindex/9)));
break;
}
}
return temp;
}

2.2.7 炮的行棋路线

炮的行棋路线相对复杂些,不过也很好处理,基本情况与车相同,只不过当你在某一方向找到第一个棋子后,设置一个判断,判断为找到一个棋子后,对空余的位置不再添加,则再找第二个棋子,如果找到第二个棋子,将第二个棋子加入索引

/**
* 炮的行棋路線
* @param posindex
* @return
*
*/
public static function initMovePao(posindex:int,pieceMap:HashMap):ArrayCollection{
var temp:ArrayCollection=new ArrayCollection();

//模方向
var findOne:Boolean=false;
for (var xr:int=posindex%9+1;xr<9;xr++)
{
if ((pieceMap.get(posindex+xr-posindex%9) as PieceVO).deskpos==-1)
{
if (!findOne)
temp.addItem(posindex+xr-posindex%9);
}
else
{
if (!findOne)
{
findOne=true;
continue;
}
else
{
//将找到子的下一子的位置也加入
temp.addItem(posindex+xr-posindex%9);
break;
}

}
}
findOne=false;
for (var xl:int=posindex%9-1;xl>=0;xl–)
{
if ((pieceMap.get(posindex+xl-posindex%9) as PieceVO).deskpos==-1)
{
if (!findOne)
temp.addItem(posindex+xl-posindex%9);
}
else
{
if (!findOne)
{
findOne=true;
continue;
}
else
{
//将找到子的下一子的位置也加入
temp.addItem(posindex+xl-posindex%9);
break;
}

}
}
//竖方向
findOne=false;
for (var yb:int=Math.floor(posindex/9)+1;yb<10;yb++)
{
if ((pieceMap.get(posindex+9*(yb-Math.floor(posindex/9))) as PieceVO).deskpos==-1)
{
if (!findOne)
temp.addItem(posindex+9*(yb-Math.floor(posindex/9)));
}
else
{
if (!findOne)
{
findOne=true;
continue;
}
else
{
//将找到子的下一子的位置也加入
temp.addItem(posindex+9*(yb-Math.floor(posindex/9)));
break;
}

}
}
findOne=false;
for (var yt:int=Math.floor(posindex/9)-1;yt>=0;yt–)
{
if ((pieceMap.get(posindex+9*(yt-Math.floor(posindex/9))) as PieceVO).deskpos==-1)
{
if (!findOne)
temp.addItem(posindex+9*(yt-Math.floor(posindex/9)));
}
else
{
if (!findOne)
{
findOne=true;
continue;
}
else
{
//将找到子的下一子的位置也加入
temp.addItem(posindex+9*(yt-Math.floor(posindex/9)));
break;
}

}
}
return temp;
}

3)中国象棋的游戏控制引擎与四国军棋并无太大差异,我们在扩展 BaseGameEngine 后,只需要覆写 CheckWin()方法,就基本可以完成大量的工作。当然,别忘了棋盘不家个初始布局,我们只需要设计一个静态布局与一个初始化方法即可。

//默认的棋盘布局
private const DEFAULT_LAYOUT:Array=[56,57,58,59,60,59,58,57,56,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,54,-1,-1,-1,-1,-1,54,-1,55,-1,55,-1,55,-1,55,-1,55,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,155,-1,155,-1,155,-1,155,-1,155,-1,154,-1,-1,-1,-1,-1,154,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,156,157,158,159,160,159,158,157,156];

public function initLayout():void{
for (var i:int=0;i<this.DEFAULT_LAYOUT.length;i++)
{
var pieceVO:PieceVO=pieceMap.get(i) as PieceVO;
pieceVO.value=DEFAULT_LAYOUT[i];
if (int(DEFAULT_LAYOUT[i])>0&&int(DEFAULT_LAYOUT[i])<100)
{
pieceVO.deskpos=getNextPlayer(1).deskpos;
}
else if (int(DEFAULT_LAYOUT[i])>=100)
{
pieceVO.deskpos=currentDeskpos;
}
}
}

在完成以上这些逻辑工作后,得到的结果就是下面这个,这个是点击炮后我们可以清楚地看到行棋路线索引。

核心逻辑基本就是这些,在这个寒冷的冬天里,我打算利用这些时间开发与优化一些有趣的棋牌类游戏,我的下一个目标是扎金花游戏,这是一个牌类游戏, 我打算接着写一个flexcardapi,当然,本文的下一节我将提供中国象棋的扩展类的设计与显示设计。如果时间允许的话,会很快,当然,如果您是一名 flex开发者,您也可以直接给我发消息,让您来完成下面的开发,我会把已经完成的设计工作教给你,您也可以学会如何使用这个api,我相信,您会找到乐 趣的,您也可以开发一些更简单或复杂的游戏,如乖三棋,动物棋,或者跳棋。

[转载]Silverlight+WCF 实战-网络象棋最终篇之解决重复的消息提示(八)

mikel阅读(888)

[转载]Silverlight+WCF 实战-网络象棋最终篇之解决重复的消息提示(八) – 路过秋天 – 博客园.

前言:

最近有网友经常会问,在跟着做象棋对战的通讯中,在重复退出进入的时候,消息会重复出现,本节就这问题进行解说与优化。

一:分析问题产生的原因?

1:首先看App.xaml,里面定义了一个全局客户端回调:

public static GameService.ServiceClient client;//回调的客户端

并且这个回调我们全局只实例化一次,并且默认加载时定位到登陆页面:

private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = root;
root.Children.Add(
new Login());//默认定位到登录页面。
}

2:再看登录页面Login.xaml里,构造函数的初始化:

public Login()
{
InitializeComponent();
App.client.LoginCompleted
+= new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_LoginCompleted);
Load();
}

我们对App.client.LoginCompleted初始化了一次事件,这时一切是正常的,接着我们进入房间,之后,我们返回系统回到登陆。

3:接着看退出系统的按钮是怎么返回到登陆页面的:

//退出系统
private void btnLogout_Click(object sender, RoutedEventArgs e)
{
if (App.chess.IsGaming)
{
btnGameLose_Click(
null, null);//发送认输
App.chess.IsGaming = false;
}
App.client.OutRoomAsync(App.player, App.player.RoomID, App.player.AttachInfo);
((App)Application.Current).RedirectTo(
new Login());
}

看最后一行,我们又New Login了,这种情况,刚才第二步中的:App.client.LoginCompleted事件将被重复注册,因此,重复的事件注册引发了重复的消息提示。

问题总结说明:

对于消息的重复提示,基本都属于事件的重复注册造成的,我们之前的代码很多转向都使用new 控件()的方式在各个页面切换时,于是容易产生这种问题。

二:解决消息重复问题

从第一步中,我们分析到问题产生的根源,于是,我们可以想出很多方式,来解决这种问题,这里我介绍两种方式:

先来看一下错误任法:注册事件前加先减,再加,示例代码如下:

App.client.LoginCompleted -= new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_LoginCompleted);
App.client.LoginCompleted
+= new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(client_LoginCompleted);

网上有人说:每次注册前先去掉一下,然后再增加,逻辑上看起来好像没问题,刚自己试了下,纯忽悠型。

现在介绍下我想到的两种方式:

1:定义全局变量List<事件名称>,保存注册过的事件名称

逻辑:在每次事件产生前,先判断一下事件是否存在,不存在则添加,存在则跳过,此方法实现简单,大伙一说就应该会了,所以直接跳过了。

2:定义全局变量保存所有房间,于是在RedirectTo切换房间的时候,避免使用New 控件() 来避免再次执行事件注册事件

下面进行代码整改:

A:App.xaml全局定义每个房间的变量,并改造成属性,所幸控件就几个,定义也费不了多少力,代码如下:

private static Login loginObj;
public static Login LoginObj
{
get
{
if (loginObj == null)
{
loginObj
= new Login();
}
//loginObj.Reset();
return loginObj;
}
}
private static Room roomObj;
public static Room RoomObj
{
get
{
if (roomObj == null)
{
roomObj
= new Room();
}
//roomObj.Reset();
return roomObj;
}
}
private static Index indexObj;
public static Index IndexObj
{
get
{
if (indexObj == null)
{
indexObj
= new Index();
}
//indexObj.Reset();
return indexObj;
}
}

说明:

这里有两点:1是改造属性方式,这样在调用时不用再写判断语句,2是注册的Reset()方法,后面会开启到。

B:查找调用RedirectTo切换界面的代码,替换为:App.xxxxObj

随便找个RedirectTo,右键查找所有引用,看看有几个要修改的地方,所幸也不多,如下图:

说明:

按上面的查找出来的代码,一个一个更改即可,如把new Index()换成App.IndexObj。

OK,消息提示重复的问题,至此,是解决了,但是,将产生一点副作用,就是切回去的时候,状态需要重置。

简单的示例说明就是:

点登陆时,把按钮设置为不可用,然后你进去,再退出,看到的是“不可用”的按钮就没法再进去了。

OK,状态重置的问题,就留到下节解决了。

本节没关联啥好看图片,就随便挂一张在下面让大伙欣赏了:

[转载]android开发我的新浪微博客户端-OAuth篇(2.1)

mikel阅读(918)

[转载]android开发我的新浪微博客户端-OAuth篇(2.1) – 遇见未知的自己 – 博客园.

本篇说说关于OAuth授权认证的事情,新浪开放api都必须在这个基础上才能调用,所以有必要专门来讲讲,前面的文章中已经提到过关于新浪微博提供了 OAuth和Base OAuth两种认证方式,并且本项目采用OAuth认证方式,至于为什么采用这个OAuth认证而不采用Base OAuth认证原因很简单,自从Twitter只支持OAuth认证方式以来,各大应用都纷纷转向OAuth认证方式,而新浪微博的开放平台也将在近日停 止Base OAuth的认证方式。

OAuth的基本概念,OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第 三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。同样新浪微 博提供OAuth认证也是为了保证用户账号和密码的安全,在这里通过OAuth建立普通新浪微博用户、客户端程序(我们正在开发的这个Android客户 端程序)、新浪微博三者之间的相互信任关系,让客户端程序(我们正在开发的这个Android客户端程序)不需要知道用户的账号和密码也能浏览、发布微 博,这样有效的保护了用户账号的安全性不需要把账号密码透露给客户端程序又达到了通过客户端程序写微博看微博目的。这个是OAuth的作用。

结合新浪微博的OAuth认证来说说具体的功能实现,首先罗列一下关键字组,下面四组关键字跟我们接下来OAuth认证有非常大的关系。

第一组:(App Key和App Secret),这组参数就是本系列文本第一篇提到的建一个新的应用获取App Key和App Secret。

第二组:(Request Token和Request Secret)

第三组:(oauth_verifier

第四组:(user_id、Access Token和Access Secret)

新浪微博的OAuth认证过程,当用户第一次使用本客户端软件时,客户端程序用第一组作为参数向新浪微博发起请求,然后新浪微博经过验证后返回第二组参数给客户端软件同时表示新浪微博信任本客户端软件,当客户端软件获取第二组参数时作为参数引导用户浏览器跳至新浪微博的授权页面, 然后用户在新浪的这个授权页面里输入自己的微博账号和密码进行授权,完成授权后根据客户端设定的回调地址把第三组参数返回给客户端软件并表示用户也信任本 客户端软件,接下客户端软件把第二组参数和第三组参数作为参数再次向新浪微博发起请求,然后新浪微博返回第四组参数给客户端软件,第四组参数需要好好的保 存起来这个就是用来代替用户的新浪账号和密码用的,在后面调用api时都需要。从这个过程来看用户只是在新浪微博的认证网页输入过账户和密码并没有在客户 端软件里输入过账户和密码,客户端软件只保存了第四组数据并没有保存用户的账户和密码,这样有效的避免了账户和密码透露给新浪微博之外的第三方应用程序, 保证 了安全性。

本项目用为了方便开发采用了oauth-signpost开源项目进行OAuth认证开发,新建OAuth.java 类文件对OA进行简单的封装,OAuth类主要有RequestAccessToken、GetAccessToken、SignRequest三个方 法,第一个方法RequestAccessToken就是上面过程中用来获取第三组参数用的,GetAccessToken方法是用来获取第四组参数 用,SignRequest方法是用来调用api用。由于采用了oauth-signpost开源项目简单了很多。具体代码如下:

代码

public class OAuth {
private CommonsHttpOAuthConsumer httpOauthConsumer;
private OAuthProvider httpOauthprovider;
public String consumerKey;
public String consumerSecret;

public OAuth()
{
// 第一组:(App Key和App Secret)
// 这组参数就是本系列文本第一篇提到的建一个新的应用获取App Key和App Secret。
this(3315495489,e2731e7grf592c0fd7fea32406f86e1b);
}
public OAuth(String consumerKey,String consumerSecret)
{
this.consumerKey=consumerKey;
this.consumerSecret=consumerSecret;
}

public Boolean RequestAccessToken(Activity activity,String callBackUrl){
Boolean ret
=false;
try{
httpOauthConsumer
= new CommonsHttpOAuthConsumer(consumerKey,consumerSecret);
httpOauthprovider
= new DefaultOAuthProvider(http://api.t.sina.com.cn/oauth/request_token,http://api.t.sina.com.cn/oauth/access_token,http://api.t.sina.com.cn/oauth/authorize);
String authUrl
= httpOauthprovider.retrieveRequestToken(httpOauthConsumer, callBackUrl);
activity.startActivity(
new Intent(Intent.ACTION_VIEW, Uri.parse(authUrl)));
ret
=true;
}
catch(Exception e){
}
return ret;
}

public UserInfo GetAccessToken(Intent intent){
UserInfo user
=null;
Uri uri
= intent.getData();
String verifier
= uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER);
try {
httpOauthprovider.setOAuth10a(
true);
httpOauthprovider.retrieveAccessToken(httpOauthConsumer,verifier);
}
catch (OAuthMessageSignerException ex) {
ex.printStackTrace();
}
catch (OAuthNotAuthorizedException ex) {
ex.printStackTrace();
}
catch (OAuthExpectationFailedException ex) {
ex.printStackTrace();
}
catch (OAuthCommunicationException ex) {
ex.printStackTrace();
}
SortedSet
<String> user_id= httpOauthprovider.getResponseParameters().get(user_id);
String userId
=user_id.first();
String userKey
= httpOauthConsumer.getToken();
String userSecret
= httpOauthConsumer.getTokenSecret();
user
=new UserInfo();
user.setUserId(userId);
user.setToken(userKey);
user.setTokenSecret(userSecret);
return user;
}

public HttpResponse SignRequest(String token,String tokenSecret,String url,List params)
{
HttpPost post
= new HttpPost(url);
//HttpClient httpClient = null;
try{
post.setEntity(
new UrlEncodedFormEntity(params,HTTP.UTF_8));
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//关闭Expect:100-Continue握手
//100-Continue握手需谨慎使用,因为遇到不支持HTTP/1.1协议的服务器或者代理时会引起问题
post.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
return SignRequest(token,tokenSecret,post);
}

public HttpResponse SignRequest(String token,String tokenSecret,HttpPost post){
httpOauthConsumer
= new CommonsHttpOAuthConsumer(consumerKey,consumerSecret);
httpOauthConsumer.setTokenWithSecret(token,tokenSecret);
HttpResponse response
= null;
try {
httpOauthConsumer.sign(post);
}
catch (OAuthMessageSignerException e) {
e.printStackTrace();
}
catch (OAuthExpectationFailedException e) {
e.printStackTrace();
}
catch (OAuthCommunicationException e) {
e.printStackTrace();
}
//取得HTTP response
try {
response
= new DefaultHttpClient().execute(post);
}
catch (ClientProtocolException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
return response;
}
}

这样就完成了OAuth功能类的开发,后面都会用到这个类相关的方法。本篇到这里就算是完结请继续关注后面的文章。