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|BuildingSudokuusingWindowsPresentationFoundationandXAML,Microsoft'snewdeclarativeprogramminglanguage.Thisisthe5tharticlefromaseriesof5articlesandfocussesonloadingandcomparingAIPlug-ins. | |
LucasMagder Difficulty:Easy TimeRequired:1-3hours Cost:Free Software: Hardware: Download: |
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:
CommentNotification
Ifyouwouldliketoreceiveanemailwhenupdatesaremadetothispost,pleaseregisterSubscribetothispost'scommentsusing
相关文章推荐
- Building a WPF Sudoku Game: Part 5 - The AI Battle: Loading and Comparing AI Plug-ins
- Building a WPF Sudoku Game: Part 4 - Building a Least Privilege Plug-in System and Even More Custom Controls (zz)
- Building a WPF Sudoku Game, Part 2: The Board UI and Validation (zz)
- Building a WPF Sudoku Game: Part 3 - Adding Polish and Customizing Controls (zz)
- Building a WPF Sudoku Game, Part 2: The Board UI and Validation
- Building a WPF Sudoku Game: Part 4 - Building a Least Privilege Plug-in System and Even More Custom Controls
- Building a WPF Sudoku Game, Part 1: Introduction to WPF and XAML
- Building a WPF Sudoku Game: Part 3 - Adding Polish and Customizing Controls
- Building a WPF Sudoku Game, Part 1: Introduction to WPF and XAML
- The Building Blocks-Enterprise Applications Part 2- Information Management and Business Analytics
- The Building Blocks-Enterprise Applications Part 3- Differentiation and Innovation
- The Building Blocks-Enterprise Applications Part 3- Differentiation and Innovation
- [INS-08106] Unexpected error occurred while loading the view 'GridPlugPlayInfoUI'
- The Building Blocks- Components of EA Part 2- Process, People, Network and Time
- The Building Blocks- Components of EA Part 2- Process, People, Network and Time
- [INS-08106] Unexpected error occurred while loading the view 'GridPlugPlayInfoUI'
- Building and using plug-ins for Android
- [翻译Building the Game: Part 5 – Static Level Geometry
- "Loading a plug-in failed The plug-in or one of its prerequisite plug-ins may be missing or damaged and may need to be reinstalled"
- The Building Blocks-Enterprise Applications Part 2- Information Management and Business Analytics