您的位置:首页 > 产品设计 > UI/UE

Building a WPF Sudoku Game: Part 5 - The AI Battle: Loading and Comparing AI Plug-ins (zz)

2007-07-26 15:16 836 查看

BuildingaWPFSudokuGame:Part5-TheAIBattle:LoadingandComparingAIPlug-ins

Published30November0606:34AM|Coding4Fun

BuildingSudokuusingWindowsPresentationFoundationandXAML,Microsoft'snewdeclarativeprogramminglanguage.Thisisthe5tharticlefromaseriesof5articlesandfocussesonloadingandcomparingAIPlug-ins.
LucasMagder

Difficulty:Easy
TimeRequired:1-3hours
Cost:Free
Software:VisualC#2005ExpressEdition.NETFramework3.0RuntimeComponentsWindowsVistaRTMSDKVisualStudioExtensionsforthe.NETFramework3.0November2006CTP
Hardware:
Download:Download(note:TasosValsamidishasanupdatedversionthatsupportsExpressionBlendhere)
Note:ThisarticlehasbeenupdatedtoworkandcompilewiththeRTMversionoftheWindowsSDK.

WelcometothefifthandfinalpartofmyWindowsPresentationFoundationtutorial!Inthistutorialwe’llbewrappingupourSudokugamebyaddingsupportforcomparingmultipleplug-ins,multiplethreads,newnotificationmessages,andacooldataboundgraphcontrol.First,let’stakealookatthenewinterfacewearetryingtobuild:



Ontheleft,thereisalistofalltheinstalledplug-ins,inthemiddle,ourgraphingcontrol,andontherightthedetailsforthecurrentplug-in.Togetthisworkingwefirstneedawaytoenumeratealltheplug-insavailabletotheapp.Thesimplestwayofdoingthisistodumpallthe.dllfilesintoadirectory,whichI’vecalled“Solvers”.It’sprettysimpletogetalistoftheassembliesinthefolder,usingthedirectoryclass:

string[]plugins=Directory.GetFiles("Solvers//","*.dll");
foreach(stringpinplugins)
{
LoadSolvers(p);
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
I’vealsoaddedanewfieldintheWindow1classtoholdthelistofloadedplug-ins,whicheventuallyendsupastheItemsSourceforthelistbox:

ObservableCollection<ISudokuSolver>Solvers=
newObservableCollection<ISudokuSolver>();


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Wealsoneedsomeothergroundworkcodetoacceptthenewplug-infolder.First,weneedtodefinethatourapplicationcanloadassembliesfromafolderotherthanitsbase(wherethe.exeis)andthesystemfolders.Wedothisbydefininganapp.config.SelecttheSudokuFXproject,rightclickandselect“Addnewitem…”then“ApplicationConfigurationFile”.Thisfileneedstobemodifiedtospecifythesubdirectoryasavalidassemblylocation:

<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBindingxmlns="urn:schemas-microsoft-com:asm.v1">
<probingprivatePath="Solvers"/>
</assemblyBinding>
</runtime>
</configuration>


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Ontopofthat,wealsoneedtoalterournewappdomain,inasimilarfashion:

AppDomainSetupads=newAppDomainSetup();
ads.ApplicationBase=AppDomain.CurrentDomain.BaseDirectory;
ads.PrivateBinPath=System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(path));
PermissionSetps=newPermissionSet(null);
ps.AddPermission(newSecurityPermission(SecurityPermissionFlag.Execution));
SolverDomain=AppDomain.CreateDomain("NewAD",null,ads,ps);


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Thenweneedtopopulateourlistwiththeproxyobjectwecreate:

Type[]ts=asm.GetTypes();
foreach(Typetints)
{
if(Array.IndexOf(t.GetInterfaces(),typeof(ISudokuSolver))!=-1)
{
Typecontainer=typeof(SudokuSolverContainer);
SudokuSolverContainerssc=SolverDomain.CreateInstanceAndUnwrap(
container.Assembly.FullName,container.FullName)
asSudokuSolverContainer;
ssc.Init(t);
Solvers.Add(ssc);
}
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Finally,asimpledatatemplateneedstobeaddedtothelistboxtodisplaytheNamepropertyoftheobject,butyoushouldbeaproatthatbynow.Ialsoaddedacustomtemplatetogivethelistitemscheckboxes,butthat’spurelycosmeticandisn’tstrictlyrequired.Next,wecanbindtherightpanel’sdatacontexttotheselecteditemintheleftlistbox,thiswayastheselectionchangestheinfowillupdate:

<StackPanelx:Name="InfoPanel"
DataContext="{BindingElementName=SolverList,Path=SelectedItem}">
<TextBlockForeground="Black"Text="SolverInfo:"FontWeight="Bold"FontSize="12"/>
<TextBlockFontSize="8"Text=""/>
<TextBlockForeground="Black"Text="{BindingPath=Name}"/>
<TextBlockForeground="Black"Text="{BindingPath=Author}"/>
<TextBlockFontSize="8"Text=""/>
<TextBlockForeground="Black"Text="{BindingPath=Description}"
HorizontalAlignment="Stretch"TextWrapping="WrapWithOverflow"/>
</StackPanel>


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Nowthatthat’sworking,letsstartbuildingthegraphcontrol.Addanewusercontroltotheproject,butthistimealterittoderivefromListBox.Wait?Thatcontrol’sjustalistbox?Yup,andnotonlythat,youcanthrowalmostanytypeofobjectinit.Howdoesitknowtheheightofthebarsthen?Theheightofeachbarisrelativetotheotherbarssincetheheightofthecontrolitselfisfinite.Theanswerisattachedproperties.Usingattachedpropertiesyoucandynamicallyaddnewpropertiestoanobjectatruntime,theonlyrequirementisthattheobjectderivesfromDependencyObject,whichinWPFmeansalmostanyobjectwillwork.Infactyou’vealreadyusedattachedproperties,DockPanel.Dockisanexampleofone.EachcontrolinaDockPanelneedstorememberwhereit’sdockedbutaddingapropertytoeachclasswouldberedundantbecausebeinginadockpanelisaspecialcase.Thisway,wecanuseapropertyofDockPanel,butstoreavalueoneachobject.Defininganattachedpropertyisverysimilartodeclaringadependencyproperty:

publicstaticreadonlyDependencyPropertyBarHeightProperty=
DependencyProperty.RegisterAttached("BarHeight",typeof(double),
typeof(DependencyObject),newPropertyMetadata(0.0));


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
but,insteadofdefiningaproperty,weneedtodefinestaticaccessmethods:

publicstaticdoubleGetBarHeight(DependencyObjectd)
{
return(double)d.GetValue(BarHeightProperty);
}

publicstaticvoidSetBarHeight(DependencyObjectd,doubleh)
{
d.SetValue(BarHeightProperty,h);
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

Now,weneedtodefinetheclasswe’regoingtostoreinthelist,thissimpleclassjustholdsareferencetothesolverused,howlongittooktorun,andwhetheritwassuccessfulinsolvingthesudoku:

publicclassSolverResult:DependencyObject
{
TimeSpantimeTaken;

publicTimeSpanTimeTaken
{
get
{
returntimeTaken;
}
set
{
timeTaken=value;
}
}

ISudokuSolversolver;

publicISudokuSolverSolver
{
get
{
returnsolver;
}
set
{
solver=value;
}
}

boolfailed;

publicboolFailed
{
get
{
returnfailed;
}
set
{
failed=value;
}
}
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Next,weneedtoaddsomecodetofigureouttheheightsofthebarsinthegraph,essentiallyIjustfindthehighestbarandassumethatitfillsthecontrolvertically,andthensetthepropertiesoneachitem.Thisonlyneedstobedonewhenthecontentschangesoconvenientlywecanoverridetheeventhandler:

protectedoverridevoidOnItemsChanged(
System.Collections.Specialized.NotifyCollectionChangedEventArgse)
{
base.OnItemsChanged(e);
longmaxVal=0;
foreach(SolverResultsinItems)
{
if(!s.Failed&&s.TimeTaken.Ticks>maxVal)maxVal=s.TimeTaken.Ticks;
}

foreach(SolverResultsinItems)
{
if(s.Failed)
{
GraphControl.SetBarHeight(s,ActualHeight-25);
}
else
{
doubleh=(double)s.TimeTaken.Ticks/
(double)maxVal*(ActualHeight-25);
if(h>10)
{
GraphControl.SetBarHeight(s,h);
}
else
{
GraphControl.SetBarHeight(s,10);
}
}
GraphControl.SetIndex(s,Items.IndexOf(s));
}
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
TheindexpropertyisanotherattachedpropertyI’vedefined;you’llseewhereit’susedinjustabit.Fornowthough,it’sallaboutthedatatemplates:

<DataTemplatex:Key="BarTemplate">
<Gridx:Name="Bar"Margin="3,3,3,0"Width="50"VerticalAlignment="Bottom"
Height="{BindingPath=BarHeight}"Background="Red">
<TextBlockx:Name="BarText"VerticalAlignment="Center"
Foreground="White"HorizontalAlignment="Center"
Text="{BindingPath=TimeTaken}">
<TextBlock.LayoutTransform>
<RotateTransformAngle="90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
<DataTemplate.Triggers>
<DataTriggerBinding="{BindingPath=Failed}"Value="True">
<SetterTargetName="Bar"Property="Opacity"Value="0.5"/>
<SetterTargetName="BarText"Property="Text"Value="Failed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Herewemakecontainerstoholdthebarsandsettheirheightsbasedonournewproperty.Ialsoaddedsometriggerstoghostoutthebarifthesolverfailedtosolvethesudokugrid.



Asyoucansee,itworksbutyuck,whatisthis,16-colorWindows3.1?We’vegottomakethislookalittlebetter.First,let’sthrowinsomekindofcolorscheme.Ok,buthowdothecolorsmaptothebars?Well,ifyou’vereadmypreviousarticle,you’veprobablyalreadyguessed:converters.Wecanlinktogetherourindexvaluewithourcolorschemeusingaconverter:

[ValueConversion(typeof(int),typeof(Color))]
publicclassBarColorConverter:IValueConverter
{
staticColor[]BarColors={
Colors.SteelBlue,
Colors.Green,
Colors.Firebrick,
Colors.DarkSlateGray,
Colors.Orange,
Colors.Khaki
};

publicobjectConvert(objectvalue,TypetargetType,objectparameter,

CultureInfoculture)
{
intv=(int)value;
returnBarColors[v%BarColors.Length];
}

publicobjectConvertBack(objectvalue,TypetargetType,objectparameter,

CultureInfoculture)
{
Colorv=(Color)value;
returnArray.IndexOf<Color>(BarColors,v);
}
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Now,ifwewanttogetfancy,it’salsopossibletodefinethisconvertersothatwhenyouuseitfromXAMLyoucandefinedthecolorschemesimilartoagradient,butthiswayworkstoo.Afterdefininganinstanceofourconverterintheresourcessectionofourcontrol,wecanuseitlikethis:

<Grid.Background>
<SolidColorBrush
Color="{BindingPath=Index,Converter={StaticResourceBarColorConverter}}"/>
</Grid.Background>


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}



That’sbetter,aboutaWindowsFormsleveloflookandfeel,butthisisn’tWindowsForms.Weneedtogivethebarsamorecomplexlookbasedontheirbasecolor.Usingconverterswecansetupabindingthatwilldothisforus.First,let’sdefineanewconverter:

[ValueConversion(typeof(Color),typeof(Color))]
publicclassColorLightnessConverter:IValueConverter
{
publicobjectConvert(objectvalue,TypetargetType,
objectparameter,CultureInfoculture)
{
if(value==null)returnnull;
intparam=int.Parse(parameter.ToString());
Colorsrc=(Color)value;
Colorret=newColor();
ret.A=src.A;
ret.R=(byte)Math.Max(Math.Min(src.R+param,255),0);
ret.G=(byte)Math.Max(Math.Min(src.G+param,255),0);
ret.B=(byte)Math.Max(Math.Min(src.B+param,255),0);
returnret;
}

publicobjectConvertBack(objectvalue,TypetargetType,
objectparameter,CultureInfoculture)
{
if(value==null)returnnull;
intparam=int.Parse(parameter.ToString());
Colorsrc=(Color)value;
Colorret=newColor();
ret.A=src.A;
ret.R=(byte)Math.Max(Math.Min(src.R-param,255),0);
ret.G=(byte)Math.Max(Math.Min(src.G-param,255),0);
ret.B=(byte)Math.Max(Math.Min(src.B-param,255),0);
returnret;
}
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Thisconverterallowsustolightenordarkenadataboundcolorasitpassesthroughthebinding.ItalsomakesuseoftheparameterfunctionalityonconverterstoallowustospecifyhowmuchwealterthecolorfromXAML.Afterwe’veaddedtheconvertertoourresourcesectioncanuseit.First,weneedtomoveoutputfromoutotherconverter,thebasecolorofthebarintosomewherewecaneasilyaccess;theTagpropertyisagreatplacetostorerandomstufflikethissolet’suseit:

Tag="{BindingPath=Index,Converter={StaticResourceBarColorConverter}}"


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Nowwecanreferencethetagandpassitthroughoursecondconverter:

<RectangleRadiusX="3"RadiusY="3"StrokeThickness="2"VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Rectangle.Stroke>
<SolidColorBrushColor="{BindingElementName=Bar,Path=Tag,
Converter={StaticResourceColorLightnessConverter},ConverterParameter=-64}"/>
</Rectangle.Stroke>
<Rectangle.Fill>
<LinearGradientBrushSpreadMethod="Repeat"MappingMode="Absolute"
StartPoint="0,0"EndPoint="1,1">
<LinearGradientBrush.Transform>
<ScaleTransformScaleX="20"ScaleY="20"/>
</LinearGradientBrush.Transform>
<LinearGradientBrush.GradientStops>
<GradientStop
Color="{BindingElementName=Bar,Path=Tag,
Converter={StaticResourceColorLightnessConverter},
ConverterParameter=-32}"Offset="0"/>
<GradientStop
Color="{BindingElementName=Bar,Path=Tag,
Converter={StaticResourceColorLightnessConverter},
ConverterParameter=-32}"Offset="0.499"/>
<GradientStopColor="{BindingElementName=Bar,Path=Tag}"Offset="0.501"/>
<GradientStopColor="{BindingElementName=Bar,Path=Tag}"Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Here,I’vecreatedagradientandborderoutlinebasedonthebasecolorbydarkeningitwithourconverter.ThisgradientaddsthestripedappearancetothegraphbarssotheMappingModepropertyisimportant,sinceitspecifiesthatourgradientissizedrelativetothescreennottheareaitisfilling.Thispreventsthestripesfromstretchinginanuglywayasthebarschangeheight.Onceyoudothisthough,thegradientnowbecomes1pointlong,hencethescaleto20points.Afteraddingsomemoreglassyoverlaysyoucanseethatthefinaleffectismuchbetter,prettycoolfordoingeverythingindatabindingeh?



Finally,wecanforceaspecialstyleontoourListBoxItems,theclassthatwrapseachiteminlisttoturnitintoacontrol:

<ListBox.ItemContainerStyle>
<StyleTargetType="{x:TypeListBoxItem}">
<SetterProperty="VerticalContentAlignment"Value="Bottom"/>
<Style.Triggers>
<TriggerProperty="IsVisible"Value="true">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationFrom="0"To="1"
Storyboard.TargetProperty="Opacity"Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Thiswaybarshangatthebottomliketheyaresupposedtoandasanaddedbonus,wecanthrowinananimationtomakethebarsfadeinastheyappear.

Nowthatthebargraphworks,howdowegetittodisplayusefulinfo?Wellthemostobviouswayittowriteamethodlikethis:

voidBenchmarkClick(objectsender,RoutedEventArgse)
{
SolverResults=newSolverResult();
s.Solver=SolverList.SelectedItemasISudokuSolver;
int?[,]arr=Board.GameBoard.ToArray();
BenchButton.IsEnabled=false;
longtick=DateTime.Now.Ticks;
s.Failed=!s.Solver.Solve(refarr);
s.TimeTaken=TimeSpan.FromTicks((DateTime.Now.Ticks-tick));
Graph.Items.Add(s);
BenchButton.IsEnabled=true;
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Theworksbutthereisaproblem:solvingagridcouldtakemorethanafewsecondsandthismethodblocks,causingtheUItofreezeuntilitreturns.Thisisabadthing.Youruserswillhateyouandyourmoretechnicaluserswillpointandlaugh(youdon’tbelieveme,butI’veseenit).Howcanwefixthis?Theansweristhreads!Unfortunately,it’snotthateasy,therearesignificantcaveatswhenwritingmultithreadedcode(whicharewaybeyondthescopeofthisarticle).Essentially,forourpurposes,theproblemliesinthatyoucan’tinteractwiththeUIobjectsfromanotherthread!Thismakesitdifficulttosay,re-enablethebuttonorupdatethegraph.Luckily,the.NETFrameworkcomestotherescuewiththeBackgroundWorkerclass,althoughthisdoesn’tcompletelysolveourproblemitprovidesaneventwhichisfiredwhenoutbackgroundtask(ourotherthread)iscomplete.SincethiseventhandlerrunsinourUIthreadwecaneasilyinteractwiththoseobjects.Tomakethingsalittlemoreuser-friendlyI’vealsoaddedanewhiddenpaneloverthesolverinformationcontrolsthatdisplaysa“pleasewait”message,whichwecanshowwhileourprocessisrunning.Thenewcodelookslikethis:


BackgroundWorkersolverWorker;

voidBenchmarkClick(objectsender,RoutedEventArgse)
{
SolverResults=newSolverResult();
s.Solver=SolverList.SelectedItemasISudokuSolver;
int?[,]arr=Board.GameBoard.ToArray();
BenchButton.IsEnabled=false;
InfoPanel.Visibility=Visibility.Hidden;
WaitPanel.Visibility=Visibility.Visible;
longtick=DateTime.Now.Ticks;
solverWorker=newBackgroundWorker();
solverWorker.DoWork+=new
DoWorkEventHandler(delegate(objectdwsender,DoWorkEventArgsdwe)
{
s.Failed=!s.Solver.Solve(refarr);
s.TimeTaken=TimeSpan.FromTicks((DateTime.Now.Ticks-tick));
});
solverWorker.RunWorkerCompleted+=newRunWorkerCompletedEventHandler(
delegate(objectrwcsender,RunWorkerCompletedEventArgsrwce)
{
Graph.Items.Add(s);
InfoPanel.Visibility=Visibility.Visible;
WaitPanel.Visibility=Visibility.Hidden;
BenchButton.IsEnabled=true;
});
solverWorker.RunWorkerAsync();
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Ifyou’reconfused,thedelegate(objectdwsender,DoWorkEventArgsdwe){…}syntaxdefinesananonymousdelegate,anew.NET2.0feature.Anonymousdelegatesaresingle-usenamelessmethods.Thiswaywecandefineoureventhandlersontheflywithoutclutteringupourclasswithextramethodsthataren’tmeanttobecalled.Also,sinceanonymousdelegateshaveaccesstothescopeinwhichtheyaredefined,wecanavoidcreatinglotsoftemporaryvariablestoholdtheobjectslocaltoourBenchmarkClickmethod.Ontopofthat,theprogramexecutionconceptuallyflowsinalinearfashionifyouignorethedefinitionsaroundthedelegates,soanonymousdelegatesalsohelpthefunctionofthemethodremainclear.Inaction,thislookslikethis,anditallowsyoukeepplayingsudokuasthesolverruns:(Itdoesrunslowonmymachinethough,becausemuchCPUtimeisspentontheabsolutelycriticalpulsatingbackgroundfeatureJ)



I’vealsoaddedsimilarthreadingcodetheboardgenerationroutinesandthe“Igiveup”buttonandalittle“x”buttontothegraphthatallowsyoutheclearthecurrentresults.Finally,asafinishingtouchlet’supdatetheextremelydated-lookingmessagebox:



Idon’tknowaboutyou,butthisdinkynon-xp-themedmessageboxdoesn’texactlyscream“awesome!”atme.Toatleastpartiallyfixthis,I’veaddedanewgridthecoverstheentirewindowandisplacedinfront.Bydefaultit’scompletelyinvisible,bysettingtoopacityto0,anddoesn’tblockinputevents,bysettingtheIsHitTestVisiblepropertytofalse.Thenwhenit’senabled,itspringsintoactionandfadesin,tintingthewindowblackanddisablingitscontrols:

<GridIsHitTestVisible="False"IsEnabled="False"x:Name="MessageLayer"
Opacity="0"HorizontalAlignment="Stretch"VerticalAlignment="Stretch"
Background="#B0000000">
<Grid.Style>
<Style>
<Style.Triggers>
<TriggerProperty="Grid.IsEnabled"Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationTo="1"Storyboard.TargetProperty="Opacity"
Duration="0:0:0.25"/>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetProperty="IsHitTestVisible"
BeginTime="0:0:0.25">
<DiscreteBooleanKeyFrameKeyTime="0"Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationTo="0"Storyboard.TargetProperty="Opacity"
Duration="0:0:0.25"/>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetProperty="IsHitTestVisible"
BeginTime="0:0:0.25">
<DiscreteBooleanKeyFrameKeyTime="0"Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}
Then,inthegrid,Iplacedoneofthecustomstyledexpandersdefinedinourresourcessectionandfilleditwithatextblock,buttonandglassyicon.AlsoIaddedanotherhiddenexpander,whichcontainsa“pleasewait”message.Afterdefiningsomenewmethodstomakeusingthesecontrolseasy:

voidShowMessage(stringm)
{
MessageText.Text=m;
MessageExpander.Visibility=Visibility.Visible;
WaitExpander.Visibility=Visibility.Hidden;
MessageLayer.IsEnabled=true;
}

voidShowWait()
{
MessageExpander.Visibility=Visibility.Hidden;
WaitExpander.Visibility=Visibility.Visible;
MessageLayer.IsEnabled=true;
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

andaneventhandlerfortheclosebuttonontheexpander

voidMessageClosed(objectsender,RoutedEventArgse)
{
MessageLayer.IsEnabled=false;
}


.csharpcode,.csharpcodepre
{
font-size:small;
color:black;
font-family:consolas,"CourierNew",courier,monospace;
background-color:#ffffff;
/*white-space:pre;*/
}
.csharpcodepre{margin:0em;}
.csharpcode.rem{color:#008000;}
.csharpcode.kwrd{color:#0000ff;}
.csharpcode.str{color:#006080;}
.csharpcode.op{color:#0000c0;}
.csharpcode.preproc{color:#cc6633;}
.csharpcode.asp{background-color:#ffff00;}
.csharpcode.html{color:#800000;}
.csharpcode.attr{color:#ff0000;}
.csharpcode.alt
{
background-color:#f4f4f4;
width:100%;
margin:0em;
}
.csharpcode.lnum{color:#606060;}

WejustneedtoreplaceMessageBox.ShowwithShowMessagetoshowafakewindow:



Hey,asIsaidbefore,atleastit’smorecolorful.

Atthispoint,withtheworkwe’vedonesofarandafewextralinesofcodeyoucanfindinthedownload,wehaveafullyfunctionalSudokugame.Ihopeyou’veenjoyedthistutorialseriesandhavesomegreatideasaboutacoolWPFappyoucanbuild!We’veonlycoveredthebasicsandthisisjustthetipoftheicebergwhenitcomestoXAMLandWinFX.There’slotsofcoolstuffleftlike3Dgraphics,videoandmultimedia,databindingtoXML,loadingXAMLatruntime,andbrowser-basedapplicationstonameafewandthat’sjustWPF!Ifyou’reitchingtocodemoreandwanttobuildontheapp,somemissingthingsyoumightwanttoworkonare:

Moreplug-ins!Itsupportsplug-insforareason!Writeabettersolverthatdoesn’ttakeforevertorun

BettermessagewindowsthatyoucanactuallymovearoundandtheabilitytodockandmovetheUIcontainers

Separatethestylesandlogicmorecleanlyandimplementskinsorcolorschemes

Addsoundeffectsormoreanimations

Re-skinALLthecontrolsforacoolerlookandfeel

Addaninternethighscoresfeatureorleaderboards

Remember,ifanybodyasks,you’renotwritingagame,you’re“updatingyourworkplaceskillstomatchevolvingtechnology”

Filedunder:puzzle,gaming

CommentNotification

Ifyouwouldliketoreceiveanemailwhenupdatesaremadetothispost,pleaseregisterhere

Subscribetothispost'scommentsusingRSS
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐