Startsidan  ▸  Texter  ▸  Teknikblogg

Anders Hesselbom

Programmerare, skeptiker, sekulärhumanist, antirasist.
Författare till bok om C64 och senbliven lantis.
Röstar pirat.

Om naken hud i Manspodden

Jag har den stora äran att spana om nakenhet i senaste avsnitt av Manspodden, vilket föder lite intressanta tankar. Klicka här för att lyssna! (Triggervarning – det blir mycket naket!)

Varför hatar troende verkligheten?

L. Aron Nelson (Aron Ra) föreläser om varför troende hatar verkligheten. Rekommenderas varmt!

Harter-Heighways drakkurva

Harter-Heighways drakkurva är en enkel och vacker linjär fraktal med många intressanta attribut. Dels är dess kontur självrepetitiv och kan pusslas ihop med lika dana konturer på många olika sätt, och linjen som kurvan består av korsar aldrig sig själv, oavsett hur lång drakkurva man väljer att rita. En drakkurva kan beskrivas som en serie av höger- och vänstersvängar. Första iterationen består av en sväng, normalt höger. Alla iterationer efter den första innehåller lika många instruktioner som den föregående, multiplicerat med två och plus 1. Alltså 1, 3, 7, 15, 31, och så vidare. Den första generationen består alltså av en högersväng (H):

H

För att skapa nästa iteration utgår man från den föregående och lägger till svängarna från den, baklänges, samtidigt som man byter ut högersvängar till vänstersvängar (V). Man avgränsar föregående iteration från den nya iterationen med en högersväng, alltså:

HHV

Tecken 1 (H) är första iterationen, nästa tecken 2 (H) är avgränsaren mellan den gamla och nya generationen, och den sista vänstersvängen (V) är vårt H från första iterationen, ersatt med ett V. Vidare, HHV baklänges blir VHH och byter man ut alla H mot V och alla V mot H så får man HVV. Då är generation tre alltså HHV, H som avgränsare och sist HVV.

HHVHHVV

Och så vidare! Nästa generation kompletteras med föregående generation baklänges, med höger ändrat till vänster och vänster ändrat till höger. Och ett H i mitten:

HHVHHVVHHHVVHVV

Ett papper kan vikas som en drakkurva, men inte i särskilt många generationer. I C# skulle man kunna använda en sådan här struktur för att beskriva ett enda steg i drakkurvan.

public struct DragonStep
{
    public DragonStep(bool isRightTurn)
            : this(isRightTurn, false) { }
    public DragonStep(bool isRightTurn,
        bool isIterationSeparator)
    {
        IsRightTurn = isRightTurn;
        IsIterationSeparator = isIterationSeparator;
    }
    public bool IsRightTurn { get; set; }
    public bool IsIterationSeparator { get; set; }
}

Egentligen behövs bara en boolesk egenskap för att hålla reda på om steget representerar en höger- eller vänstersväng, men jag vill hålla reda på var en generation börjar och slutar för att kunna rendera den med olika färger.

Här är listklassen som anropas för att bygga kurvan. Funktionen GetDragonSequence skapar en sekvens av höger och vänster-svängar i form av en lista av strukturen vi nyss såg. När man fått listan, handlar det bara om att välja en startposition och en startriktning, och sedan svänga åt höger eller vänster enligt vad som står i listan.

public class DragonCurveGenerator
{
    public static List<DragonStep> GetDragonSequence(int generations)
    {
        var ret = new List<DragonStep>();
        if (generations <= 0)
            return ret;
        if (generations > 0)
            ret.Add(new DragonStep(true));
        if (generations <= 1)
            return ret;
        for (var i = 1; i < generations; i++)
        {
            var lastGenerationFinalIndex = ret.Count - 1;
            ret.Add(new DragonStep(truetrue));
            for (var x = lastGenerationFinalIndex; x >= 0 ; x--)
                ret.Add(new DragonStep(!ret[x].IsRightTurn));
        }
        return ret;
    }
}

Jag definierar upp de möjliga riktningarna med en enumeration, för enkelhetens skull:

public enum Direction { Up, Right, Down, Left }

Sen är det bara en fråga om att rita kurvan, alltså följa sekvensen av svängar. Här får varje sväng representeras av en pixel. Notera att jag utnyttjar avgränsaren mellan iterationer för färgsättning.

public class DragonCurveRenderer
{
    public static void Draw(List<DragonStep> steps,
        Graphics g, Point startPosition, Direction startDirection,
        Brush color1, Brush color2, int first)
    {
        if (steps == null || steps.Count <= 0)
            return;
        var direction = startDirection;
        var x = startPosition.X;
        var y = startPosition.Y;
        var color = color1;
        var index = 0;
        foreach (var step in steps)
        {
            if (step.IsIterationSeparator)
            {
                color = color == color1 ? color2 : color1;
                index++;
            }
            if (index >= first)
                g.FillRectangle(color, x, y, 1, 1);
            ApplyDirection(direction, ref x, ref y);
            if (step.IsRightTurn)
            {
                if ((int) direction < 3)
                    direction++;
                else
                    direction = Direction.Up;
            }
            else
            {
                if (direction > 0)
                    direction--;
                else
                    direction = Direction.Left;
            }
        }
    }
    private static void ApplyDirection(Direction direction,
        ref int x, ref int y)
    {
        switch (direction)
        {
            case Direction.Up:
                y--;
                break;
            case Direction.Right:
                x++;
                break;
            case Direction.Down:
                y++;
                break;
            case Direction.Left:
                x--;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}

Bilden föreställer en drakkurva på 18 generationer där generation 6 (med index 5) är den förste som ritas ut. Alltså totalt 13 fält, varannan är vit, varannan är svart. Så här kan användningen se ut (Windows Forms):

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace DragonCurve
{
    public partial class Form1 : Form
    {
        private List<DragonStep> DragonSteps { get; set; }
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            DragonSteps = DragonCurveGenerator.GetDragonSequence(18);
        }
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            var startPoint = new Point(550, 450);
            DragonCurveRenderer.Draw(DragonSteps, e.Graphics,
                startPoint, Direction.Up, Brushes.White, Brushes.Black, 5);
        }
    }
}

E.T. for Atari 2600

The Atari 2600 was a home gaming console from 1977. The 2600 was a rather primitive machine compared to the later Commodore 64 (1982), but it supported hardware sprites and color graphics, which the Sinclair ZX didn’t. But the introduction price was rather high. $199 back then corresponds to more than $800 today.

Many games were released for the 2600, but one of the most talked about, E.T., was a huge commercial failure. You play as the alien E.T. on Earth, and the objective of the game is to help E.T. get home. Every move you make costs energy, but you can pick up pieces of candy to gain energy.

First, you need to pick up three parts that make up a special phone that can be used to contact the home planet. You can swap 9 pieces of candy for a part, if you like. The parts are found in pits that you fall into, and can levitate out of. To levitate, press fire so that E.T.’s neck is stretched, and when the neck is stretched, you simply levitate higher by pushing the joystick forward.

E.T. has three lives. E.T.’s friend Elliot can bring him back to life when he dies. There is a special object that persuades Elliot to give E.T. a fourth life if it is carried. The game contains two protagonists, a scientist and an FBI agent, who can capture E.T. or steal phone parts from him. If you manage to find all three phone parts, you need to locate a place where the phone works. And after finding this place, you have to locate the place where the spaceship lands, and stay there and await the ship. If you succeed, you progress to the next level where the objective is the same, but the conditions are harder.

The game looks quite good for its time, but it is it is rather buggy and unpredictable. You can fall into a pit without any warning, and it is sometimes rather hard to get out of a pit without falling in again. The best thing about this game, is that you can chat with a friend while playing – the game does not require full attention. This video shows how to complete the first level:

Grisen i säcken

26 år har passerat sedan Galenskaparna och Aftershave hade premiär med sin revy Grisen i säcken, och jag har precis sett om den versionen jag bandade på VHS i mitten av 90-talet. Revyn består av tre akter, där akt två och tre håller det format som vi är vana att se från gruppen – ett slags sofistikerat studentspex. Men det är första akten som gör denna revy speciell, och därför lämnar jag övrigt okommenterat. Den första akten är en sammanhängande musikal som gruppen själva beskriver som en dansbandsopera, viket flörtar med den för tiden populära genren rock opera, som representerades av t.ex. Pete Townshend, Savatage, Dave Clark och Styx.

Den utspelar sig 1991, vilket var året revyn hade premiär. 1991 var ett turbulent år i politiken. Ekonomin hade kraschat och stödet Socialdemokraterna var lågt. De förlorade makten till Moderaterna under Carl Bildt. Kristdemokraterna kom in i riksdagen, tillsammans med missnöjespartiet Ny Demokrati som styrdes av Ian Wachtmeister och Bert Karlsson. Det är Ny Demokratis uppgång och fall som utgör handlingen för akten – eller möjligtvis partiet Ny Deodorant som existerar i ett parallellt universum, under ledning av Rune och sedermera greven Douglas. Att Rune motsvarar Bert och Douglas motsvarar Ian är helt uppenbart för den som sett Grisen i säcken, men jag nämner det ifall du inte sett den och ändå vill förstå vad jag pratar om.

Budskapet i första akten är fortfarande aktuellt, och många händelser går att applicera på Sverigedemokraterna, men förutsättningarna är helt annorlunda. Bert och Ian (och så även Rune och Douglas) var företagsamma och mer eller mindre framgångsrika entreprenörer, medan Sverigedemokrater är frustrerade gräsrötter, helt utan tidigare erkännande i samhället de verkar i. Ny Demokrati (och även Ny Deodorant) havererade redan under första mandatperioden.

Rimligen kan inte Galenskaparna och Aftershave ha känt till hur historien om Ny Demokrati skulle sluta, när de lät sitt fiktiva parti Ny Deodorant haverera – Rune och Douglas blev tillslut programledare i tv. I valet 1994 fick Ny Demokrati 1,4 procent av väljarstödet och både Bert och Ian hoppade av. Ian fick en talkshow på TV3, “I grevens tid”, medan det gullades med Bert i Melodifestivalen – inget förlåter rasistisk politik som exploaterandet av unga artister.

När jag såg Grisen i säcken för första gången var jag V-väljare, och jag applåderade den politiska träffsäkerheten. Idag tillhör jag den politiska mitten, men tycker ändå att detta verk har åldrats väl. Jag behöver inte hålla med om allt för att förstå Claes Erikssons briljanta manus och gruppens utmärkta framförande. Men med det sagt, så delar jag förmodligen Erikssons syn på Ny Demokrati. Och kanske även på Sverigedemokraterna, men det vet jag egentligen inte.

Fem inlägg om C# på Nethouse-bloggen

Jag har skrivit fem inlägg om C# på Nethouse blogg som jag tänkte be att få dela med mig av.

Kortare kod med mönstermatchning i C# 7

Förenklad hantering av funktioner som producerar multipla värden i C#

Skalning i MonoGame

Late binding i C#

Lazy evaluated string interpolation

Uppdatering 2017-09-22:

Kort kod och syftningsfel

Uppdatering 2018-01-26:

Microsoft Cognitive Services: Computer Vision

Spaning: Framtiden för handdatorn

Min första mobiltelefon var någon gammal Ericsson som både lät mig ringa (för hutlös minuttaxa) och skicka SMS. Min första handdator* var en svartvit Sony CLIÉ med Palm OS. Den virtuella upplösningen var 320 x 320 punkter, men den fysiska upplösningen var på 640 x 640 punkter, vilket innebar att den kunde återge vektorgrafik och text betydligt bättre än bitmapsbilder. Dessutom kunde den visa 16 gråskalor. Man kunde surfa på Internet genom att koppla den till sin PC:s USB-bort och ladda hem de sidor man ville läsa senare under dagen.

Idag har dessa två enheter fusionerats till en enda enhet, som i princip är en handdator med en ringfunktion**. Många har försökt, men det var Apple som lyckades göra denna nya produkt till en kommersiell succé i slutet på 00-talet. När man idag säger “mobil” eller “telefon” så menar man en handdator som har ringfunktion (som numera knappt används). Vill man förtydliga, kan man säga “smart telefon”, men idag är det nästan som att säga “platt-tv”.

Det var inte förrän Apple gav sig in i leken som handdatorn med ringfunktion blev var mans ägo, och det var inte heller förrän Apple gav sig in i leken med produkten iPad som “surfplattan” blev en kommersiellt framgångsrik produkt. Skillnaden mellan en smart telefon och en surfplatta är att surfplattan saknar ringfunktion, och är därmed alltså en handdator.

När Microsoft på allvar ger sig in i matchen med en surfplatta uppstod förvirringen på allvar. Microsofts Surface Pro saknar tangentbord, precis som iPad, och den har touchscreen precis som iPad. Men eftersom den levereras med bättre anslutningsmöjligheter till andra enheter och med ett bredare utbud av mjukvara antas den istället vara “en dator”, vilket omkullkastar allt jag hittills sagt.

För rent tekniskt, är inte en surfplatta en telefon utan ringfunktion, utan en dator. Precis på samma sätt som en smartphone faktiskt är en dator med ringfunktion. Och eftersom dessa enheter är datorer, så är det hårdvaran och operativsystemet som påverkar deras användbarhet. En iPad har ett operativsystem (IOS) som utvecklats för informationskonsumenter, som sakta rör sig mot att även kunna tillgodose producenter. En Surface Pro har ett operativsystem som utvecklats för informationsproducenter, som sakta för sig mot att även tillgodose konsumenter. Men det är fortfarande datorer!

Nu när intresset för att ringa telefonsamtal har minskat, och Nokia släppt en traditionell mobiltelefon som kostar några hundralappar, ser jag två potentiella konsekvenser. För det första tror jag att intresset för telefoni minskar bland både producenter och konsumenter av dyra handdatorer. För det andra tror jag att den som vill kunna ringa, i framtiden kommer att köpa en telefon (i ordets traditionella bemärkelse), typ en Nokia 3310.

Apples enorma framgång med iPhone kommer göra att det annalkande paradigmskiftet dröjer, men när det väl kommer, tror jag att det innebär att den lilla handdatorn tappar sin ringfunktion, men erhåller egenskaper som gör att den ersätter PC:n. När man kör Visual Studio, Cubase, SQL Server, med mera, i sin “telefon man inte kan ringa med”, så är det “telefonen” man dockar in till skärm och tangentbord på jobbet. Men det är inte den man kommer att ringa med. Om man vill ringa, så köper man en Nokia 3310.

*) En handdator är en mycket portabel dator – så pass portabel att den kan bäras i en hand när den är i drift. En handdator har tryckkänslig skärm (touchscreen) och möjlighet till batteridrift.

**) Inte ens respektabla Cambridge Dictionary innehåller en definition av ett telefonsamtal (alltså nyttjandet av en ringfunktion), utan nöjer sig med att säga att ett telefonsamtal är vad som sker när man “använder en telefon”.

Från första försöket att spela in avsnitt 200

Från första försöket att spela in avsnitt 200 av Radio Houdi:

Starquake

Original soundtrack by Steve Crow.

Commodore 128 sprites

Ski Dance

Title image by Cheap Snow Gear Ltd.

Ski Dance cover, original by Chris Huelsbeck (1986).

Mac vs. PC

MacWorld presenterar “11 klockrena argument” för att Mac är bättre än PC. Rent tekniskt är en Mac en PC från Apple, men det som menas här är att en Mac är bättre än en annan PC, som t.ex. en Microsoft Surface, en HP Elitebook eller en Chromebook. För detta levereras 11 “klockrena” argument (som alla utom det 4:e är korrekta), vars styrka och kvalité jag tänker nosa lite på, genom att jämföra dem med Microsoft.

1. Mac OS främjar hälsan

När OS X var nytt, var det extremt buggigt, men nu är det hyfsat stabilt. Det är viktigt för användarens mentala hälsa. Dessutom har inte mycket förändrats, vilket inte ställer frustrerande krav på användaren. Windows 8 såg inte alls ut som Windows 7, vilket var frustrerande. Detta stämmer! Däremot kan man inte säga att en modern Macbook är mindre kraschbenägen än t.ex. en modern Surface – idag hittar du Windows t.ex. i atomubåtar och i kärnkraftverk.

2. Ekosystem i världsklass

En Mac och en Iphone samspelar bra, men det råder inga som helst tvivel om att både Google och Microsoft har kommit längre än Apple i arbetet med att fusionera sina plattformar, men Apple är fortfarande världsklass med en hedrande tredje plats.

3. Färre val gör dig lyckligare

Få modeller ger få val, vilket ger mindre beslutsångest. Man skriver att Apple har sju datorer i produktion – fyra bärbara och tre stationära. Microsoft har sin Surface Pro i lite olika utföranden, en Surface Book och en Surface Studio, och det är väldigt tydligt vilken produkt som vänder sig till vilken användare. Om färre val gör dig lyckligare, blir man då ännu lyckligare av ännu färre val? Då vinner Microsoft över Apple, men här jämför man sig med Lenovo, HP eller Dell, som alla har ett större produktutbud.

4. Du slipper virus

Det du inte vet, mår du inte dåligt av – när säkerhet verkligen är ett krav, hittar man nästan alltid Windows eller Linux.

5. Du får grymma program på köpet

När man jämför mjukvaruutbudet på Windows Phone med IOS så vinner Apple. Jämför man Windows 10 med Mac OS så vinner Windows 10. Utbudet av kvalitativ mjukvara som antingen ingår i Windows-licensen eller finns som open source för Windows, är oslagbart. Men om färre val gör dig lyckligare, så är det Windows Phone och Mac OS som tar hem priset.

6. Du får vad du betalar för

“Vill du ha en dator som baseras på den senaste tekniken och har en elegant design är Apples datorer ett utmärkt val. Men visst, om det viktiga är en billig dator som klarar surfande och Facebook finns det bättre alternativ.” En Mac är inte speciellt prisvärd eller up to date, och ofta blir de tiotusentalskroners Facebook-maskiner.

7. Det bara fungerar 

Allt fungerar out of the box, men det kan vara svårt att byta ut komponenter på egen hand. Detta är inte unikt för Apple. Jag kan t.ex. inte byta batteri på min Surface Pro, men självklart levereras en märkesdator fullt installerad, utan bloatware. Det gäller naturligtvis även Microsoft.

8. Skärmarna är fantastiska

Det råder inga tvivel om att Microsoft är stråt vassare här, men skärmarna är bra! De har dessutom touch, stöd för penna och Surface Dial.

9. Fri telefonsupport

Du kan inte ringa Microsoft hur som helst när du vill ha support, du får nöja dig med att ta det i text om det ska vara gratis. Här vinner Apple, något som du förmodligen betalar för när du köper adaptrar till överpris – there is no such thing as a free lunch.

10. Touch Bar är en innovation

Det är verkligen mer innovativt att dra och rita direkt på skärmen, men visst, detta är en innovation. Inte alla modeller har någon touch bar, dock.

11. Du kommer att bli nöjd

Om du köper en produkt som är dyr, så kommer du intala dig att du är nöjd, av psykologiska skäl. Så du kommer troligen bli nöjd, av helt fel orsak.

Min slutsats är att vi inte har att göra med “11 klockrena argument”, men väl “10 argument”.

Två nya SID-låtar

Jag har slängt ihop två nya SID-låtar som kan avnjutas på din Commodore 64.

Den ena (the_roger_boogie.sid) är ett resultat från ett demo-jam på jobbet tillsammans med Klas Dahlén, Roger Johansson och Erik Sandberg. Den är egenkomponerad och använder en samtida playerrutin (SID 8580). Förhandsgranskning finns på YouTube.

Den andra, som är en cover på låten I Touch Myself av Divinyls, slände jag ihop igår kväll. Den körs på en rutin som är anpassad till den gamla brödburken (SID 6582).

Ladda gärna hem filerna här!

Preservera program utan specialutrustning

Här försöker jag visa hur man kan bevara sina gamla Commodore-program, trots att man inte äger någon specialutrustning för ändamålet.

Entity Framework 6

Entity Framework 6 (EF6) är mycket enkelt att komma igång med. För att få tillgång till aktuell version, använd Nuget-paketet EntityFramework. Den här bloggposten visar det minsta man behöver veta för att komma igång med EF6 i sitt program.

Install-Package EntityFramework

Dessutom behöver du ha en tom databas att jobba mot. I detta exempel antar jag att databasen heter EfTest. Anslutningssträngen lagras i konfigurationsfilen. I mitt fall är det en vanlig Console Application.

  <connectionStrings>
    <add name="EfTest" providerName="System.Data.SqlClient" connectionString="Data Source=XXX;Initial Catalog=EfTest;Integrated Security=True" />
  </connectionStrings>

Nästa steg är att skapa DbContext-objektet. Denna ärver från System.Data.Entity.DbContext. Basklassen behöver veta vilken anslutningssträng som ska användas. Antingen skickar man in själva anslutningssträngen, eller så hänvisar man till ett namn. De tabeller som ska användas beskrivs av klasser och väljs in som argument till den generiska DbSet (också i System.Data.Entity). Här har jag helt enkelt skapat två klasser som heter Tabell1 och Tabell2.

    public class TestContext : DbContext
    {
        public TestContext() : base("name=EfTest")
        {
        }
        public DbSet<Tabell1> Tabell1 { get; set; }
        public DbSet<Tabell2> Tabell2 { get; set; }
    }

Låt oss titta på klassen Tabell1. Den innehåller en primärnyckel (int) och en sträng som heter Value. Attributet Key på fältet (System.ComponentModel.DataAnnotations) anger att motsvarande kolumn i databasen är en primärnyckel. Attributet DatabaseGenerated (System.ComponentModel.DataAnnotations.Schema) använder jag här för att säga att kolumnen ska vara en räknare, och alltså inte något som jag ska tillhandahålla data till vid tillägg. Det innebär alltså att nya poster skapas genom att Value får ett värde, inte Tabell1Id.

    using (var db = new TestContext())
    {
        db.Tabell1.Add(new Tabell1 {Value = "Hej"});
        db.SaveChanges();
    }

När tabellen läses av, kan man konstatera att nyckelkolumnen i detta fall ska ha fått sitt värde ändå.

    using (var db = new TestContext())
    {
        foreach (var tabell1 in db.Tabell1)
        {
            Console.WriteLine($"{tabell1.Tabell1Id} - {tabell1.Value}");
        }
    }

Det är när programmet körs första gången som databasen byggs upp automatiskt. Om en befintlig tabell förändras, t.ex. genom att klassen Tabell1 får en till property (vilket motsvarar en till kolumn) kommer programmet inte längre att fungera. Då måste en strategi för uppgradering tillhandahållas. T.ex. kan man säga att databasschemat helt enkelt ska genereras om vid förändring. Det innebär att man förlorar allt data, så med denna strategi vill man förmodligen komplettera med kod som lägger in lite testdata.

Database.SetInitializer(new DropCreateDatabaseIfModelChanges<TestContext>());

För att tillhandahålla testdata, skriv en egen initializer, baserad på den som du vill använda, och tillhandahåll testdata i funktionen Seed.

    public class DropCreateInitializer : DropCreateDatabaseIfModelChanges<TestContext>
    {
        protected override void Seed(TestContext context)
        {
            context.Tabell1.Add(new Tabell1 { Value = "Hej" });
            context.Tabell1.Add(new Tabell1 { Value = "Oj" });
        }
    }

Sen är det bara att använda sin egna initializer.

Database.SetInitializer(new DropCreateInitializer());

Fullskärm med exakt virtuell bildupplösning

Om du vill bygga ett spel med Monogame som 1) körs i fullsärmsläge och 2) har en låg virtuell upplösning, typ 320 x 200 pixlar, så finns en enkel lösning. Istället för att rendera spelet på din backbuffer, så renderar du till en render target som är just 320 x 200 pixlar stor. För att åstadkomma detta, skapa en ny property i ditt spel, av typen RenderTarget2D.

private RenderTarget2D renderTarget { get; set; }

I konstruktorn, givet att vi befinner oss i release-kompileringen, skapar man en backbuffer som är lika stor som skärmen.

            graphics = new GraphicsDeviceManager(this);
#if DEBUG
            ScreenWidth = 640;
            ScreenHeight = 400;
            graphics.PreferredBackBufferWidth = ScreenWidth;
            graphics.PreferredBackBufferHeight = ScreenHeight;
            graphics.IsFullScreen = false;
#else
            ScreenWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
            ScreenHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            graphics.PreferredBackBufferWidth = ScreenWidth;
            graphics.PreferredBackBufferHeight = ScreenHeight;
            graphics.IsFullScreen = true;
#endif
            Content.RootDirectory = "Content";

Funktionen Initialize är en lämplig plats att skapa RenderTarget2D-objektet. Det är storleken på den som styr den virtuella upplösningen.

renderTarget = new RenderTarget2D(GraphicsDevice, 320, 200);

Slutligen renderas spelet mot RenderTarget2D-objektet. Funktionen Draw skulle kunna se ut så här:

protected override void Draw(GameTime gameTime)
{
    graphics.GraphicsDevice.SetRenderTarget(renderTarget);
    graphics.GraphicsDevice.Clear(Color.Black);

    //Rita spelet här!
    spriteBatch.Begin();
    spriteBatch.Draw(hej, new Vector2(0, 0), Color.White);
    spriteBatch.Draw(hej, new Vector2(310, 190), Color.White);
    spriteBatch.End();

    graphics.GraphicsDevice.SetRenderTarget(null);
    spriteBatch.Begin(samplerState: SamplerState.PointClamp);
    spriteBatch.Draw(renderTarget, new Rectangle(0, 0, ScreenWidth, ScreenHeight), new Rectangle(0, 0, 320, 200), Color.White);
    spriteBatch.End();
    base.Draw(gameTime);
}

Notera det sista anropet på spriteBatch.Draw, den tredje sista raden. Den skalar upp spelet till den riktiga back bufferten. Om man vill ha rektangulära pixlar, ska det hanteras där. Denna exempelkod tar inte hänsyn till olika bildförhållanden.

Så här ser hela koden ut:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{
    public class Game1 : Game
    {
        private RenderTarget2D renderTarget { get; set; }
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        private int ScreenWidth { get; }
        private int ScreenHeight { get; }
        private Texture2D hej;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
#if DEBUG
            ScreenWidth = 640;
            ScreenHeight = 400;
            graphics.PreferredBackBufferWidth = ScreenWidth;
            graphics.PreferredBackBufferHeight = ScreenHeight;
            graphics.IsFullScreen = false;
#else
            ScreenWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
            ScreenHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            graphics.PreferredBackBufferWidth = ScreenWidth;
            graphics.PreferredBackBufferHeight = ScreenHeight;
            graphics.IsFullScreen = true;
#endif
            Content.RootDirectory = "Content";
        }
        protected override void Initialize()
        {
            renderTarget = new RenderTarget2D(GraphicsDevice, 320, 200);
            base.Initialize();
        }
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            hej = Content.Load<Texture2D>("hej");
        }
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();
            base.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.SetRenderTarget(renderTarget);
            graphics.GraphicsDevice.Clear(Color.Black);

            //Rita spelet här!
            spriteBatch.Begin();
            spriteBatch.Draw(hej, new Vector2(0, 0), Color.White);
            spriteBatch.Draw(hej, new Vector2(310, 190), Color.White);
            spriteBatch.End();

            graphics.GraphicsDevice.SetRenderTarget(null);
            spriteBatch.Begin(samplerState: SamplerState.PointClamp);
            spriteBatch.Draw(renderTarget, new Rectangle(0, 0, ScreenWidth, ScreenHeight), new Rectangle(0, 0, 320, 200), Color.White);
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Jag utvecklar detta på min arbetsgivares blogg.

Skala upp Monogame

När man bygger retrospel med en upplösning på omkring 320 x 200 pixlar, vill man gärna skala upp spelet så att sprites och andra objekt inte blir så små på en modern skärm. För den som programmerar Monogame finns flera lösningar. Jag tycker att den enklaste metoden är att använda ett transform matrix. När jag debuggar mitt spel, tycker jag att en uppskalning på 3 x 3 är rätt lämplig, vilket innebär att ett fönster på 960 x 600 pixlar är lämpligt. Detta är spelets konstruktor, spelet har kompletterats med en medlemsvariabel vid namn transformMatrix av typen Matrix.

graphics.PreferredBackBufferWidth = 960;
graphics.PreferredBackBufferHeight = 600;
graphics.IsFullScreen = false;
transformMatrix = Matrix.CreateScale(960 / 320, 600 / 200, 1.0f);

I skarpt läge låter jag skärmupplösningen styra skalningen, eftersom jag vill att spelet ska köras i fullskärm.

var width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
var height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
graphics.PreferredBackBufferWidth = width;
graphics.PreferredBackBufferHeight = height;
graphics.IsFullScreen = true;
transformMatrix = Matrix.CreateScale(width / 320, height / 200, 1.0f);

För att effekten ska slå igenom, ska Matrix-objektet skickas med till SpriteBatch-objektets Begin-metod. För att undvika oönskad kantutjämning använder jag en sampler state som heter PointClamp.

spriteBatch.Begin(transformMatrix: transformMatrix, samplerState: SamplerState.PointClamp);

Ett exempelspel skulle kunna se ut så här:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Matrix transformMatrix;
        Texture2D testtexture { get; set; }
        private int x = 0;
        private int y = 0;
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
#if DEBUG
            graphics.PreferredBackBufferWidth = 960;
            graphics.PreferredBackBufferHeight = 600;
            graphics.IsFullScreen = false;
            transformMatrix = Matrix.CreateScale(960 / 320, 600 / 200, 1.0f);
#else
            var width = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
            var height = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            graphics.PreferredBackBufferWidth = width;
            graphics.PreferredBackBufferHeight = height;
            graphics.IsFullScreen = true;
            transformMatrix = Matrix.CreateScale(width / 320, height / 200, 1.0f);
#endif
            Content.RootDirectory = "Content";
        }
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            testtexture = Content.Load<Texture2D>("my_folder");
        }
        protected override void Update(GameTime gameTime)
        {
            var state = Keyboard.GetState();
            if (state.IsKeyDown(Keys.Escape))
                Exit();
            if (state.IsKeyDown(Keys.Left))
                x--;
            else if (state.IsKeyDown(Keys.Right))
                x++;
            if (state.IsKeyDown(Keys.Up))
                y--;
            else if (state.IsKeyDown(Keys.Down))
                y++;
            base.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin(transformMatrix: transformMatrix, samplerState: SamplerState.PointClamp);
            spriteBatch.Draw(testtexture, new Vector2(x, y), Color.White);
            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Ett problem som man bör känna till, är att om bildförhållandet mellan den virtuella upplösningen och den fysiska upplösningen inte är samma, så kommer fler pixlar än önskat att synas. När man kör i fönster, så kan man styra detta så att bildförhållandet är detsamma. 960 x 600 har samma bildförhållande som 320 x 200. Men i fullskärmsläge bör man ha detta i åtanke.

Jag utvecklar detta på min arbetsgivares blogg.

Commoflage 76

Ett sjukvart långt avsnitt av Commoflage som (främst) handlar om Lazy Jones! Lyssna här, mycket nöje!

Labyrint (recursive backtracking)

Jag har börjat arbeta med ett datorspel i kategorin roguelike. Varje spel ska vara unikt, så kartan genereras när en användare spelar spelet. Vid uppstart skapar jag en labyrint som sen används som kontur för rummen som genereras efter behov. De rum som spelaren inte besöker behöver man trots allt inte hålla i RAM, men jag vill ha konturen färdig för att kunna ha lite kontroll över kartans kvalité.

 
Totalt okunnig om labyrintalgoritmer läste jag igenom Wikipedias artikel om recursive backtracking som i korthet går ut på följande:
1. Utgå från den cell som ska vara startpunkten, C.
 
2. Om C inte är vald sedan tidigare, plocka bort en slumpvis utvald vägg på C. Följ den nyskapade öppningen och låt den vara C.
 
3. Om nya C har varit C tidigare, stega bakåt till första cell som fortfarande har fyra väggar, gör den till C, upprepa steg 2.
 
4. Om C nu är samma som var C vid punkt 1 så är labyrinten klar.
 
Så i princip startar mitt (blivande) datorspel med en hel hög av celler (precis hur många jag vill) som håller information om in- och utgångar (konturen), men det är först när cellen används som fyller jag på med s.k. tiles, alltså de byggstenar som bestämmer kartans detaljer. Låt oss titta på själva labyrinten, och lämna resten därhän.
För detta exempel valde jag språket C#. Trots att språket är lite pratigt, är det ganska flexibelt och användbart.

Först av allt behövs en entitet – en cell – som kan ha väggar i fyra riktningar: Norr, öst, söder och väst. Jag anger att jag både vill kunna läsa (get) och skriva (set) dessa egenskaper under programmets gång. Initialt måste cellen ha alla sina fyra möjliga väggar.

public class Cell
{
    public bool WallNorth { get; set; } = true;
    public bool WallEast { get; set; } = true;
    public bool WallSouth { get; set; } = true;
    public bool WallWest { get; set; } = true;
}

Vidare måste cellen kunna svara på var i labyrinten den befinner sig. Det behövs för att cellens grannar ska kunna hittas. En cell som inte kan svara på det, får inte skapas över huvudet taget.

    public int X { get; }
    public int Y { get; }
    public Cell(int x, int y)
    {
        X = x;
        Y = y;
    }

Eftersom cellen enligt algoritmens regler måste kunna svara på hur många väggar den har, måste vi beskriva hur vi kan veta det.

    public int WallsCount
    {
        get
        {
            var c = WallNorth ? 1 : 0;
            c += WallEast ? 1 : 0;
            c += WallSouth ? 1 : 0;
            c += WallWest ? 1 : 0;
            return c;
        }
    }

Och slutligen måste cellen kunna berätta om sin position i ett lämpligt format.

    public Point Position => new Point(X, Y);

Därefter behövs en representation av själva labyrinten som kan hålla en matris av celler. Denna gång väljer jag att skapa en liten labyrint med 100 celler (10 x 10). Bredden och höjden måste kunna läsas (get) under programmets gång. Koden skapar inte cellerna, endast platshållaren för dem.

public class Labyrinth
{
    public static int Width { get; } = 10;
    public static int Height { get; } = 10;
    public Cell[,] Cell = new Cell[Width, Height];
}

Sen ska vi beskriva algoritmen i labyrintens konstruktor (=funktion med ansvar för initiering). Vi börjar med att berätta att vi vill kunna skapa slumptal, och att platshållaren för våra celler verkligen ska innehålla celler.

        var rnd = new Random();
        for (var y = 0; y < Height; y++)
            for (var x = 0; x < Width; x++)
                Cells[x, y] = new Cell(x, y);

Därefter måste vi definiera konceptet med grannar för konstruktorn. Grannar är angränsande celler.

        Func<int, int, List<Point>> getNeighbours = (x, y) =>
        {
            var ret = new List<Point>();
            if (y > 0 && Cells[x, y - 1].WallsCount >= 4)
                ret.Add(Cells[x, y - 1].Position);
            if (x < Width - 1 && Cells[x + 1, y].WallsCount >= 4)
                ret.Add(Cells[x + 1, y].Position);
            if (y < Height - 1 && Cells[x, y + 1].WallsCount >= 4)
                ret.Add(Cells[x, y + 1].Position);
            if (x > 0 && Cells[x - 1, y].WallsCount >= 4)
                ret.Add(Cells[x - 1, y].Position);
            return ret;
        };

Sen måste vi beskriva hur man slår in en vägg. Det är inte så enkelt som att sätta en cells vägg-egenskap (t.ex. WallNorth) till false, eftersom grannen åt det hållet också måste få sin motsvarande vägg (t.ex. WallSouth) satt till false.

        Action<Cell, Point> knockWall = (cell1, cell2) =>
        {
            if (cell2.Y < cell1.Y)
            {
                cell1.WallNorth = false;
                Cells[cell2.X, cell2.Y].WallSouth = false;
                return;
            }
            if (cell2.X > cell1.X)
            {
                cell1.WallEast = false;
                Cells[cell2.X, cell2.Y].WallWest = false;
                return;
            }
            if (cell2.Y > cell1.Y)
            {
                cell1.WallSouth = false;
                Cells[cell2.X, cell2.Y].WallNorth = false;
                return;
            }
            if (cell2.X < cell1.X)
            {
                cell1.WallWest = false;
                Cells[cell2.X, cell2.Y].WallEast = false;
                return;
            }
        };

Nu har konstruktorn tillgång till resurserna, och den vet vad en granne är och hur man slår in en vägg. Dags att sätta igång! Vi behöver hålla koll på vilka celler vi besökt…

            var queue = new Queue<Cell>();

…och så behöver vi en slumpvis utvald cell att börja i.

            var c = Cells[rnd.Next(Width), rnd.Next(Height)];
            queue.Enqueue(c);

Därefter, slå ut en vägg och följ efter. Om det inte går, backa till start och prova igen.

            while (queue.Count > 0)
            {
                var neighbours = getNeighbours(c.X, c.Y);
                if (neighbours.Count > 0)
                {
                    var neighbour = neighbours[rnd.Next(neighbours.Count)];
                    knockWall(c, neighbour);
                    queue.Enqueue(c);
                    c = Cells[neighbour.X, neighbour.Y];
                }
                else
                {
                    c = queue.Dequeue();
                }
            }

 
För att representera detta på skärmen, behöver jag bara bekymra mig om två av väggarna, höger och under. Högra grannens vänstervägg och undre grannens övre vägg har samma status (tack vare knockWall).
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        const int size = 20;
        for (var y = 0; y < Labyrinth.Height; y++)
            for (var x = 0; x < Labyrinth.Width; x++)
            {
                if (Labyrinth.Cells[x, y].WallEast)
                    e.Graphics.DrawLine(Pens.Black, x * size + (size - 1), y * size, x * size + (size - 1), y * size + (size - 1));
                if (Labyrinth.Cells[x, y].WallSouth)
                    e.Graphics.DrawLine(Pens.Black, x * size, y * size + (size - 1), x * size + (size - 1), y * size + (size - 1));
            }
    }
 
Hela källkoden ser ut så här:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private Labyrinth Labyrinth { get; } = new Labyrinth();
        public Form1()
        {
            InitializeComponent();
        }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        const int size = 20;
        for (var y = 0; y < Labyrinth.Height; y++)
            for (var x = 0; x < Labyrinth.Width; x++)
            {
                if (Labyrinth.Cells[x, y].WallEast)
                    e.Graphics.DrawLine(Pens.Black, x * size + (size - 1), y * size, x * size + (size - 1), y * size + (size - 1));
                if (Labyrinth.Cells[x, y].WallSouth)
                    e.Graphics.DrawLine(Pens.Black, x * size, y * size + (size - 1), x * size + (size - 1), y * size + (size - 1));
            }
    }
    }
    public class Cell
    {
        public bool WallNorth { get; set; } = true;
        public bool WallEast { get; set; } = true;
        public bool WallSouth { get; set; } = true;
        public bool WallWest { get; set; } = true;
        public int X { get; }
        public int Y { get; }
        public Cell(int x, int y)
        {
            X = x;
            Y = y;
        }
        public int WallsCount
        {
            get
            {
                var c = WallNorth ? 1 : 0;
                c += WallEast ? 1 : 0;
                c += WallSouth ? 1 : 0;
                c += WallWest ? 1 : 0;
                return c;
            }
        }
        public Point Position => new Point(X, Y);
    }
    public class Labyrinth
    {
        public static int Width { get; } = 10;
        public static int Height { get; } = 10;
        public Cell[,] Cells = new Cell[Width, Height];
        public Labyrinth()
        {
            var rnd = new Random();
            for (var y = 0; y < Height; y++)
                for (var x = 0; x < Width; x++)
                    Cells[x, y] = new Cell(x, y);
            Func<int, int, List<Point>> getNeighbours = (x, y) =>
            {
                var ret = new List<Point>();
                if (y > 0 && Cells[x, y - 1].WallsCount >= 4)
                    ret.Add(Cells[x, y - 1].Position);
                if (x < Width - 1 && Cells[x + 1, y].WallsCount >= 4)
                    ret.Add(Cells[x + 1, y].Position);
                if (y < Height - 1 && Cells[x, y + 1].WallsCount >= 4)
                    ret.Add(Cells[x, y + 1].Position);
                if (x > 0 && Cells[x - 1, y].WallsCount >= 4)
                    ret.Add(Cells[x - 1, y].Position);
                return ret;
            };
            Action<Cell, Point> knockWall = (cell1, cell2) =>
            {
                if (cell2.Y < cell1.Y)
                {
                    cell1.WallNorth = false;
                    Cells[cell2.X, cell2.Y].WallSouth = false;
                    return;
                }
                if (cell2.X > cell1.X)
                {
                    cell1.WallEast = false;
                    Cells[cell2.X, cell2.Y].WallWest = false;
                    return;
                }
                if (cell2.Y > cell1.Y)
                {
                    cell1.WallSouth = false;
                    Cells[cell2.X, cell2.Y].WallNorth = false;
                    return;
                }
                if (cell2.X < cell1.X)
                {
                    cell1.WallWest = false;
                    Cells[cell2.X, cell2.Y].WallEast = false;
                    return;
                }
            };
            var queue = new Queue<Cell>();
            var c = Cells[rnd.Next(Width), rnd.Next(Height)];
            queue.Enqueue(c);
            while (queue.Count > 0)
            {
                var neighbours = getNeighbours(c.X, c.Y);
                if (neighbours.Count > 0)
                {
                    var neighbour = neighbours[rnd.Next(neighbours.Count)];
                    knockWall(c, neighbour);
                    queue.Enqueue(c);
                    c = Cells[neighbour.X, neighbour.Y];
                }
                else
                {
                    c = queue.Dequeue();
                }
            }
        }
    }
}
Och detta är resultatet:
 

Labyrinth game: Canned Beer

Canned Beer, labyrinth game made in MonoGame. Requires Windows 10. Happy New Year!

Download here!

Arkanoid (Martin Galway cover)

Ghostbusters (2016)

Paul Feig lämnade nästan garantier på att rebooten av Ghostbusters skulle vara dålig. En kass trailer, en hip hop-remix av soundtracket, rasstereotyper, könsstereotyper och en provokativ damage control. Under resans gång lyckades Reddit publicera filmens synopsis och regissör Paul Feig lät hälsa att den så kallade “nördkulturen” innehåller några av planetens minst sympatiska människor.

Det sista vill jag kommentera, eftersom nördkulturen är något som ligger mig varmt om hjärtat. Jag är inte någon nörd, ens i ordets nya, positiva bemärkelse. Visst har jag fastnat lite i gamla Commodore-maskiner och Linda och Valentin-böckerna, men det jag gillar med nördkulturen är de riktiga nördarna. Och jag känner tillräckligt många för att kunna säga att man får leta på andra platser om man verkligen vill hitta några av planetens minst sympatiska människor.

Tvärt emot det intryck som trailern gav, så fungerade faktiskt många skämt. Det havererade lite när karaktären Kevin anställdes mot Abbys vilja, för att både Erin och Jillian hade mindre professionella motiv att vilja anställa honom. Den situationen hade kanske varit ännu värre om spökligan vore män som anställde en kvinna.

Trots att filmen hade högre budget än originalet, var specialeffekterna märkbart sämre. Personkemin mellan spökjägarna fungerade sisådär, men var och en var de jättebra. Regin var undermålig, vilket är det absolut bästa man kan förvänta sig av Feig.

En av de viktigaste orsakerna till att den nya Ghostbusters-filmen inte behöll samma spökliga som vi såg på bio 1984, är naturligtvis Harold Ramis bortgång. Men vi fick faktiskt se resten av gänget, Bill Murray, Ernie Hudson och Dan Aykroyd, skymta förbi. Förmodligen under tvång, med tanke på filmens rykte. Jag klickade på 6 av 10 på IMDb, och bidrog därmed till att (i skrivande stund) höja filmens snitt.

Uppdatering 2017-08-10: Mr. Plinkett’s Ghostbusters (2016) Review

Blockera The Pirate Bay?

Min första Cordova-app

Denna bloggpost är ett resultat av mitt första försök att skapa en plattformsoberoende app med Visual Studio 2015 och Cordova, som pratar med ett Web API.

När man väljer att skapa en Cordova-app i Visual Studio, får man med lite kod som man kanske inte vill ha med när man testar lite själv. Jag valde att rensa innehållet i den div vars klass heter “app”, helt tömma CSS-filen från innehåll, och rensa upp allt utom “pause” och “resume” från index.js.

Därefter installerade jag jQuery och jQuery Mobile. Här finns det två saker att tänka på. För det första passar inte nödvändigtvis den senaste jQuery Mobile ihop med den senaste jQuery. Efter en googling kunde jag konstatera att version 1.4.5 av jQuery Mobile fungerar med version 2.1.4 av jQuery, så dessa installerade jag.

Install-Package jquery -version 2.1.4
Install-Package jquery.mobile -version 1.4.5

Det andra att ha i åtanke är att script-filerna installeras i mapparna Scripts respektive Content. Men i en Cordova-app ska dessa ligga under www/scripts respektive www/css, så man måste manuellt flytta dem dit, genom att dra och släppa i Solution Explorer. Sen är det bara att inkludera i head-sektionen på index.html.

<script type="text/javascript" src="scripts/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="scripts/jquery.mobile-1.4.5.min.js"></script>

Inuti min div (“app”) placerar jag två bilder, “firstScreen” och “secondScreen”, där jag låter den första innehålla ett enkelt formulär och den andra vara dold.

<div id="firstScreen">
    <form target="http://localhost:64924/api/Values" method="get">
        <input type="text" id="p1" name="p1" />
        <br />
        <input type="text" id="p2" name="p2" />
        <br />
        <input type="button" onclick="submitForm();" />
    </form>
</div>
<div id="secondScreen" style="display: none;">
    Hello!
</div>

Koden för att skicka innehållet i formuläret till servern, antar att jag har ett enkelt Web API igång. Om det gick bra, presenterar jag svaret på den nya sidan och visar den. Om det inte gick bra, skriver jag fail i en textbox.

function submitForm() {
    var x = { p1: $("#p1").val(), p2: $("#p2").val() };

    $.post('http://localhost:64924/api/Values/', x, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } })
    .success(function(data, status) {
        $("#firstScreen").hide();
        $("#secondScreen").text(data);
        $("#secondScreen").show();
    })
    .error(function(data, status) {
        $("#p1").val("Fail!");
    });
}

Cordova-appen pekar ut en ASP.NET Web API-applikation, som är väldigt lik exempelapplikationen som man får om man väljer att skapa en sådan i Visual Studio 2015. Jag pekar på den exempelcontroller som följde med, Values. Eftersom jag gör en http-post förväntar jag mig att metoden Post ska svara. Argumentet som jag tar emot, har två properties vars namn stämmer överens med datat i formuläret: p1 och p2. Funktionen gör just inget annat än att skicka tillbaka strängen “From server!” till Cordova-appen.

public string Post([FromBody]Testmeddelande value)
{
    return "From server!";
}

Därmed har jag en enkel plattformsoberoende app som kan samla in och skicka data, samt ta emot och presentera svaret.

Funktioner på Vic-20

SQL Server Dependency Browser

SQL Server Dependency Browser for 2012 or later is made open source here.

Installation: Sql2012DepSetup.exe

Lite nyheter i C# 7: Lokala funktioner

Till en viss grad har man kunnat skapa lokala funktioner i C# under en tid.

public void DoSomething()
{
    Func<int, int> d = (x) => x * 2;
    for (var i = 1; i <= 8; i++)
        Console.WriteLine(d(i));
}

I C# 7 kan man skapa fullvärdiga funktioner i en annan funktion. Det enda som egentligen skiljer en lokal funktion från en medlemsfunktion (eller en static) är att lokala funktioner inte har någon angiven synlighet, eftersom det inte är relevant.

public void DoSomething()
{
    int d(int x) => x * 2;
    for (var i = 1; i <= 8; i++)
        Console.WriteLine(d(i));
}

Fördelen med riktiga lokala funktioner är att det ger tillgång till parameterarrayer, referensparametrar, utparametrar och generics, alltså sådant som inte kunde användas i gamla inline-funktioner.

Closure-hanteringen är lite intressant. Följande exempel använder bara gammal C# och kompilerar inte, eftersom variabeln MinFunktion som håller inlinefunktionen inte finns än.

public void DoSomething()
{
    MinFunktion();
    int x = 10;
    Action MinFunktion = () => x++;
    Console.WriteLine(x);
}

Om vi istället flyttar ner anropet, skrivs 11 ut på skärmen, eftersom x ökas med 1 i MinFunktion.

public void DoSomething()
{
    int x = 10;
    Action MinFunktion = () => x++;
    MinFunktion();
    Console.WriteLine(x);
}

Resultatet blir detsamma om jag ersätter MinFunktion med en fullvärdig lokal funktion. Det x som ökas i MinFunktion är en closure.

public void DoSomething()
{
    int x = 10;
    void MinFunktion() => x++;
    MinFunktion();
    Console.WriteLine(x);
}

Men detta är riktigt intressant. Eftersom MinFunktion nu är en fullvärdig funktion, så skapas den i den yttre funktionen DoSomething vid kompilering. Så nu kan jag anropa den innan variabeln x finns.

public void DoSomething()
{
    MinFunktion();
    int x = 10;
    void MinFunktion() => x++;
    Console.WriteLine(x);
}

Svaret blir då 10, eftersom det inte är closure-variabeln x som blir ökad, utan en x som är lokal för MinFunktion.

Slutligen, eftersom lokala funktioner är fullvärdiga funktioner, kan de innehålla egna lokala funktioner. Snyggt!

Så vad fungerar inte?

Generellt sett kan en värdetyp inte innehålla null, men en referenstyp kan innehålla null. Detta är vettigt, eftersom en referenstyp innehåller just en referens som kan vara eller inte vara satt.

Vi har haft nullable types i C# en tid nu. Det är en struktur som ger värdetyper en null-flagga, så att dessa kan vara null.

public int X { get; set; } //Ej initierad - innehåller 0.
public int? Y { get; set; } //Ej initierad - innehåller null.

Snart ska man kunna förhindra referenstyper från att vara null, vilket är praktiskt t.ex. om man vill att en parameter ska vara obligatorisk. Troligen kommer det att se ut ungefär så här:

public MyType A { get; set; } //Kan innehålla null
public MyType! B { get; set; } = new MyType(); //Kan inte innehålla null

C# 7 ska ha visst stöd för pattern matching, men det fungerar inte (än).

Inte heller record types fungerar. Record types är klasser som endast innehåller properties, som deklareras med en enda rad kod.

Det finns alltså anledning att titta med på C# 7 längre fram.

Lite nyheter i C# 7: Tuples

Om jag vill skapa en funktion som skickar tillbaka två värden, kan det lösas med en tuple. I .NET Framework är tuples implementerat som en generisk klass. Denna funktion skickar tillbaka en int och en string:

public static Tuple<int, string> DoSomething()
{
    return Tuple.Create(10, "Hello");
}

Ett anrop som tar emot svaren skulle kunna se ut så här. I detta fall är response av typen Tuple<int, string>, vilket innebär att Item1 är en int och Item2 en string.

var response = DoSomething();
Console.WriteLine(response.Item1); //10
Console.WriteLine(response.Item2); //"Hello"

Eftersom C# 7 har inbyggt stöd för tuples, behöver jag inte hänvisa till klassen Tuple. Det går helt enkelt att deklarera fler returer på en funktion.

Samma funktion i C# 7 kan se ut så här (givet att NuGet-paketet System.ValueTuple är på plats):

public static (int, string) DoSomething()
{
    return (10, "Hello");
}

Koden som utför anropet kan se lika dan ut. Variabeln response är nu en (int, string) istället för en Tuple<int, string>.

Funktionens returvärden kan dessutom ha specificerade namn, vilket gör koden mer självdokumenterande.

public static (int i, string s) DoSomething()
{
    return (10, "Hello");
}

Anropet:

var response = DoSomething();
Console.WriteLine(response.i); //10
Console.WriteLine(response.s); //"Hello"

Tuples kan skapas inline (x nedan), inline med namngivna medlemmar (y nedan) eller inline med type inference (z nedan).

(int, int, int) x = (10, 20, 30);
Console.WriteLine(x.Item1); //10

(int kalle, int pelle, int sven) y = (10, 20, 30);
Console.WriteLine(y.kalle); //10

var z = (10, 20, 30);
Console.WriteLine(z.Item1); //10

Introduktion till evolutionära algoritmer

Commodore 64 del 2

Del 1 finns här.

Commodore 64 del 1

Del 2 finns här.

Föreläsning om Commodore 64

Tisdagen den 27 september kl. 18:00 bjuder ABF i Örebro på en föreläsning om Commodore 64, i ABF:s lokaler på Fredsgatan 18 i Örebro.

Commodore 64 är fortfarande världens mest sålda hemdator, och trots att det gått 34 år sedan den introducerades på marknaden, släpps fortfarande nya spel till systemet, som fortfarande imponerar när det gäller grafik och ljud. Commodore 64 slog inte bara konkurrenterna Sinclair, Texas Instruments och Atari på fingrarna under 80-talet, utan lyckades överleva dem alla. Vad var det som gjorde att just Commodore 64 engagerade så många användare, programmerare, grafiker och musiker? Vad kunde datorn som inga andra system klarade av? Och vad är det som fortfarande lockar med just Commodore 64?

Föreläsare är Anders Hesselbom som har varit hemdatoranvändare sedan 1982 och idag är en inbiten programmerare och Commodore-entusiast.
I denna föreläsning berättar han varför just Commodore 64 sticker ut i jämförelse med andra system.

Hörselslinga finns.
Fika finns till försäljning

Facebook-event finns här. Föreläsningen är gratis och öppen för allmänheten. Välkommen!

Commoflage 69

Jag hade den stora äran att bli inbjuden till podcasten Commoflage avsnitt 69, där vi bl.a. pratar Ghostbusters. Och som vanligt spelas det en hel del bra C64-musik. Klicka här för att komma till avsnittet.

12116295_1507265992904073_867537746_o

Jag kommenterar även den infekterade Ghostbusters-debatten på min blogg.

Captured (Lars Hård)

An attempt to cover the C64 classic Captured soundtrack by Lars Hård.

Om du vill höra mer musik, mitt album från 2004 (tillsammans med Tommy Deile) finns på nätet här: Single Point of Failure – This time we are both

Mina C64-covers finns här: http://www.remix64.com/act/anders-hesselbom/

Dessutom, om du vill lyssna på mitt sommarprat i podcasten Kvack, om bl.a. min musik, så finns den här: http://kvackyou.se/2016/07/kvacksnack-sommar-2016-anders-hesselbom/

Freddie Mercury – Mr. Bad Guy (1985)

En kort recension av Freddie Mercurys “Mr. Bad Guy” från 1985.

Hur kan vi utnyttja evolutionen?

Tack vare den ofrånkomliga utveckling som följer från förändring över tid, med selektion, och tack vare att vem som helst idag har tillgång till starka datorer, kan vi tämja evolutionen till att göra lite vad vi vill. Låt säga att jag t.ex. vill skapa ett korsord från en lista av ord. Antingen sätter jag mig ner och flätar orden manuellt, vilket kan vara nog så svårt. Eller så använder jag min PC för att skapa en algoritm som beskriver hur orden ska flätas ihop, vilket inte heller är speciellt enkelt.

Fördelen med evolutionära algoritmer är att jag bara behöver veta vad jag vill åstadkomma – jag behöver inte tänka på hur något görs. Om det är ett korsord som ska byggas, så skulle jag kunna tänka mig att mitt indata är t.ex. 20 – de ord som ska flätas ihop till ett korsord. Om jag kan definiera vad ett bra korsord är, så kan jag göra ett urval. Och kan jag göra ett urval, så kan jag låta slumpen göra jobbet. Jag behöver även ha två regler: en regel som säger att t.ex. ordet ASKA inte får korsas med ordet PILBÅGE, och en regel som säger att om ordet ASKA ska korsas t.ex. med ordet MATROS, så måste det ske på någon av de gemensamma bokstäverna (vilket är S eller A).

En tänkt initiering av korsordet skulle kunna vara att skapa 15 giltiga korsord genom att låta slumpen avgöra om ett ord ska vara vågrätt eller lodrätt, och sedan placera ut ordet på en tänkt matris, på en slumpvis utvald plats. Om ordet inte kan placeras på den plats som tilldelas, så slumpar vi fram en ny plats, så att reglerna upprätthålls. Detta upprepas för varje ord till dess att alla ord ligger på en giltig plats, vilket innebär att vi har ett giltigt korsord, om än förmodligen ett väldigt spretigt sådant.

Dessa 15 korsord kan vi betrakta som en lista av föräldrar. Jag brukar jobba med två generationer – en föräldrageneration och en barngeneration. Och det är från någon av dessa 15 korsord som det perfekta korsordet ska komma.

Nästa steg blir att låta varje korsord få 3 barn. Ett barn i detta fall är en kopia av ett korsord, med en slumpmässig förändring. Man kan låta slumpen avgöra om man ska positionera om ett ord, två ord eller tre ord. Man kan låta slumpen avgöra om något eller alla av dessa ord ska förvandlas från vågrätt till lodrätt, eller tvärt om. Vi som bygger evolutionära algoritmer har lånat en biologisk term för detta skeende, nämligen mutation. Barnen, 45 stycken, lagras i listan som representerar barngenerationen.

Varje barn poängsätts efter hur väl det är anpassat till sin miljö (där miljö egentligen är mina kriterier). En liten area ger högre poäng än en stor area, antal intersektioner mellan ord ger poäng, en mer rektangulär form ger högre poäng än en mer långsmal form, och så vidare. När alla 45 barnkorsord är poängsatta, sparar jag de bästa 15 i föräldragenerationen, tömmer barngenerationen, och upprepar processen.

Över tid kommer kvalitén (alltså likheten med mina kriterier) att förbättras, men om tillräckligt lång tid får passera så upphör förbättringen. Man kan tänka sig ett tröskelvärde som säger att om inte något korsord har förbättrats på, säg, 10 000 generationer, så är vi nöjda med vårt korsord – det korsord som har högst poäng.

Och därmed har jag skapat ett korsord utan att själv vara förmögen att varken placera ut orden på egen hand eller beskriva den algoritm som skulle göra detta. Allt samman bygger på att nästan var och varannan person idag, i sin ficka, förvaltar något som för 20 år sedan skulle betraktas som en superdator.

.NET Sprite

Hårdvarurenderade sprites i C#:

Github: https://github.com/r1sc/Sprite
Nuget: https://www.nuget.org/packages/Sprite/

…och så några “bloopers“…

Shadow of the Beast

My cover version of the soundtrack to the Amiga version of Shadow of the Beast, written by David Whittaker.

Pengar är ingen motivator

Arbetslösheten i Sverige har ökat stadigt sedan början av 70-talet, då den var 2%. Idag får vi räkna med att staten kostar pengar, att kommunerna kostar pengar, och att varje medborgare dessutom måste försörja 1/10 av en medmänniskas utgifter. Mycket få arbetslösa blir motiverade av betald utbildning och löfte om anställning – de som finner detta attraktivt återfinns bland den redan arbetande skaran.

Till en viss grad kan man lösa problem med statens finanser genom att höja skatten. T.ex. anser rikspolischef Dan Eliasson att vi har för få poliser, vilket rimligtvis är ett problem som kan lösas med skattehöjningar, om inte omprioriteringar.

Jag är själv anhängare av hypotesen att skatt kan vara ett styrmedel, vilket innebär att högre beskattning ger lägre konsumtion. I Sverige praktiseras detta på bensin, tobak och alkohol, men jag vet att idén inte är helt okontroversiell. Det finns dem som anser att hög skatt odiskutabelt leder till ökade inkomster för stat eller kommun. Rimligtvis anser alla att det finns en gräns, om inte annat när man passerar 100% inkomstskatt, och måste betala mer än man tjänar. Men de som kritiserar denna idé anser att man i alla tillstånd under 100% kan öka statens intäkter genom att höja skatten. Hur det än ligger till med denna sak, så är vi tvungna att förhålla oss – pengar är inte alltid en motivatör.

Som invånare i Mellansverige kan jag konstatera att en arbetssökande “hen” här, har anmält till diskrimineringsombudsmannen (DO) att hen kallats för hon. Jag försvarar hens rätt att bli kränkt till döden, men om en kränkning ska anmälas till DO, har jag svårt att se vilket jobb som är lämpligt för hen. Det ligger i sakens natur att en anställning följer till att göra saker man kanske inte alltid vill göra, och att man utsätter sig för saker man inte alltid vill utsätta sig för. Även jag själv kan ibland sitta och skriva kod som visualiserar affärslogik medan jag drömmer om att bygga ett actionspel där rymdskepp jagas av laserkanoner. Och jag tänker inte låta mig övertalas att det finns någon hen i hela världen som har haft en mindre komplicerad uppväxt än vad jag själv har haft – jag råkar bara ha en omodern könsidentitet.

Aftonbladet: Kallade “hen” för “hon” – anmäls för diskriminering

Hur hittar spöket Pac-Man?

En mycket enkel men mycket effektiv algoritm som används i Pac-Man.

.NET Core Hello World

C# 16 år!

List invocations in constructor

This example shows how to list the method invocations in a C# file. This is the file that will be read:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
  public class MyClass
  {
    public MyClass()
    {
      Console.WriteLine("Hello.");
      DoSomething();
      DoSomethingElse();
    }

    private void DoSomething()
    {
    }

    private void DoSomethingElse()
    {
    }
  }
}

To install CodeSearchTree in your Visual Studio project, type Install-Package CodeSearchTree in the package manager console. Your project must use .NET Framework 4.6 or higher.

The path to the first method invocation is ns/cls/constructor/block/expression/invocation, but since each invocation is located under an expression node, we will iterate through those. The code will list the three calls made in the constructor of the example class above.

//Load the C# class file.
var tree = CodeSearchTree.Node.CreateTreeFromFile(@"MyClass.cs");

//Get the first expression in the constructor.
var exp = tree.GetChild("ns/cls/constructor/block/expression");

while (exp != null)
{
  //Get the invocation and type out the source code.
  var invocation = exp.GetChild("invocation");
  if (invocation != null)
    Console.WriteLine(invocation.Source);

  //Get the next node. Note that the next node might be of any kind.
  exp = exp.GetNextSibling();
}

If you like to use the typed version of the CodeSearchTree API, your code should look like this:

//Load the C# class file.
var tree = CodeSearchTree.Node.CreateTreeFromFile(@"MyClass.cs");

//Get the first expression in the constructor.
var exp = tree.Ns.Cls.Constructor.Block?.Expression?.SearchResult;

while (exp != null)
{
  //Get the invocation and type out the source code.
  var invocation = exp.Invocation?.SearchResult;
  if (invocation != null)
    Console.WriteLine(invocation.Source);

  //Get the next node. Note that the next node might be of any kind.
  exp = exp.GetNextSibling();
}

The output in both cases will be the source code for the three invocations. But let’s say that you want the function names only. The names will be found in different places for different invocations. The first invocation a memberaccess node and two id nodes. The first is called Console and the second is the name of the function. The two other invocations consists of only one id node. We must look at two places. This code will list the three function names only.

//Load the C# class file.
var tree = CodeSearchTree.Node.CreateTreeFromFile(@"MyClass.cs");

//Get the first expression in the constructor.
var exp = tree.GetChild("ns/cls/constructor/block/expression");

while (exp != null)
{
  //Get the invocation and type out the source code.
  var invocation = exp.GetChild("invocation");
  if (invocation != null)
  {
    var id = invocation.GetChild("memberaccess/id[1]");
    if (id != null)
      Console.WriteLine(id.Name);
    else
    {
      id = invocation.GetChild("id");
      if (id != null)
        Console.WriteLine(id.Name);
    }
  }
  //Get the next node. Note that the next node might be of any kind.
  exp = exp.GetNextSibling();
}

Again, if you like to use the typed version of the CodeSearchTree API, your code should look like this:

//Load the C# class file.
var tree = CodeSearchTree.Node.CreateTreeFromFile(@"MyClass.cs");

//Get the first expression in the constructor.
var exp = tree.Ns.Cls.Constructor.Block?.Expression?.SearchResult;

while (exp != null)
{
  //Get the invocation and type out the source code.
  var invocation = exp.Invocation?.SearchResult;
  if (invocation != null)
  {
    //No need for SearchResult property since the indexer gives the search result.
    var id = invocation?.MemberAccess?.Id[1];
    if (id != null)
      Console.WriteLine(id.Name);
    else
    {
      id = invocation?.Id?.SearchResult;
      if (id != null)
        Console.WriteLine(id.Name);
    }
  }

  //Get the next node. Note that the next node might be of any kind.
  exp = exp.GetNextSibling();
}

Output:

WriteLine
DoSomething
DoSomethingElse

Project website: https://github.com/Anders-H/CodeSearchTree

Gosbust 0.9.8

I have uploaded the latest version of Gostbust (0.8.9). The next thing to do is to build a proper tokenizer, and I thought I would publish the current version before starting that.

Download it here.

Parse Transact SQL

To parse T-SQL from C#, add a reference to the following libraries (listed under Extensions, not under Framework):

Microsoft.Data.Schema.ScriptDom
Microsoft.Data.Schema.ScriptDom.Sql

My examples imports the following namespaces:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Data.Schema.ScriptDom;
using Microsoft.Data.Schema.ScriptDom.Sql;
using System.IO;

This code shows how to parse a boolean expression:

//Parse
var p = new TSql100Parser(false);
IList<ParseError> err;
var sql = "(a > 10) and (i < 10)";
var exp = p.ParseBooleanExpression(new StringReader(sql), out err);

//Errors? Empty if none.
Console.WriteLine($"{err.Count} errors.");
err.ToList().ForEach(x =>
   Console.WriteLine(
      $"Error \"{x.Message}\" at line {x.Line}."));

//Result. Null if errors.
if (!(exp == null))
   exp.ScriptTokenStream.ToList().ForEach(x =>
      Console.WriteLine(
         $"Token type: {x.TokenType}, source: {x.Text}"));

If you want to parse a complete Transact SQL program, call the ParseStatementList function instead of the ParseBooleanExpression function.

//Parse
var p = new TSql100Parser(false);
IList<ParseError> err;
var sql = "SELECT EmployeeID, FirstName FROM dbo.Employees WHERE FirstName LIKE 'N%'";
var exp = p.ParseStatementList(new StringReader(sql), out err);

Console.WriteLine($"{err.Count} errors.");
err.ToList().ForEach(x =>
   Console.WriteLine(
      $"Error \"{x.Message}\" at line {x.Line}."));

//Result. Null if errors.
if (!(exp == null))
   exp.ScriptTokenStream.ToList().ForEach(x =>
      Console.WriteLine(
         $"Token type: {x.TokenType}, source: {x.Text}"));

This is the expected output:

sqlparse

Förberäknade spritebanor på Commodore 128

Med anledning av hur långsam Commodore 128 var på att beräkna multiplikation och division, detta klipp visar hur man kan förberäkna banor för sprites.

CodeSearchTree use case: Read constant values

For this example, I am using Visual Studio 2015 Community edition to create a console application.  Make sure that you are using .NET Framework 4.6 or later. Let’s say that you have a .cs file with constants that looks something like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MySystem
{
  public class Settings
  {
    public const int Iterations = 56;
    public const string TargetFilename = @"C:\Output\log.txt";
    public const int Length = 128;
    public const bool Repeat = true;
  }
}

You want to know the names and values in this file. Let’s start from the beginning. In my console application, I want a reference to the CodeSearchTree – I accept your complements for my choice of name. Just to be on the safe side, I specify the name of the project that use the CodeSearchTree library from when I add the package in the Package Manager Console.

Get-Project MyProject | Install-Package CodeSearchTree

In the Main function, first declare the filename variable, then:

//Load and parse the source file.
var tree = CodeSearchTree.Node.CreateTreeFromFile(filename);
//Get the first constant.
var f = tree.GetChild("ns/cls/field");
//Display name and value and get the next constant.
while (!(f == null))
{
  if (f.NodeType == CodeSearchTree.NodeType.FieldDeclarationSyntaxNode)
  {
    //Display...
    var n = f.Name;
    var x = "vardeclaration/vardeclarator/equalsvalue/literal";
    var v = f.GetChild(x).Source;
    Console.WriteLine($"Name: {f.Name}, Value: {v}");
  }
  //Get next...
  f = f.GetNextSibling();
}

If you are unsure about the path strings in your C# tree, try to drop your file on the test client. Happy searching!

AND, OR och EOR på MOS 6502

Eftersom det är fredagskväll så kan jag bjuda på lite “fyllekodning”. AND, OR och EOR på MOS 6502 , mycket nöje!

Apple I

There are an estimated six working Apple 1 machines in the world today. When sold, around 1976, the manual were more detailed on how the chips work than how to update ones Facebook status.

Lathund XAML: Reflektera förändringar vid data binding

En enkel databindning (alltså databindning av en post) åstadkommer man i WPF genom att tilldela ett objekt till DataContext för containern av kontrollerna som ska bindas. Här har jag tilldelat ett namn till Grid-elementet som ligger på huvudfönstret från början, och placerat en TextBox däri som jag binder till propertyn FirstName.

<Grid Name="theGrid">
   <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="283,103,0,0"
   TextWrapping="Wrap" VerticalAlignment="Top" Width="120"
   Text="{Binding FirstName}" />
</Grid>

Givet att programmet har tillgång till klassen Employee (förra exemplet) och att fönstret har en medlemsvariabeln (emp) av den typen, så kan vi i t.ex. Initialized-eventet göra tilldelningen till DataContext.

this.emp = new Employee() { FirstName = "Sven" };
this.theGrid.DataContext = this.emp;

Om vi under resans gång försöker läsa av propertyn FirstName, så kommer eventuella ändringar som användaren gjort i det bundna textfältet att ha reflekterats tillbaka till propertyn. Om vi är intresserade av att validera inmatat data behöver vi kunna registrera valideringsregler från programmet på kontrollens container. I mitt lilla exempelprojekt heter root-namnrymden WpfApplication1.

<Grid Name="theGrid" xmlns:val="clr-namespace:WpfApplication1">

Klassen som sköter valideringen måste ärva från klassen ValidationRule. Notera att jag kallar valideringsklassen för NameValidationRule.

class NameValidationRule : System.Windows.Controls.ValidationRule
{
   public int MinLength { get; set; }
   public string ErrorMessage { get; set; }

   public override ValidationResult Validate(object value, CultureInfo cultureInfo)
   {
      var s = (value as string == null ? "" : value as string).Trim();
      if (s.Length < this.MinLength)
         return new ValidationResult(false, this.ErrorMessage);
      return new ValidationResult(true, null);
   }
}

Därefter använder jag den XML-namnrymd jag tidigare registrerade (val) när jag registrerar klassen NameValidationRule.

<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="283,103,0,0"
   TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
   <Binding Path="FirstName">
      <Binding.ValidationRules>
         <val:NameValidationRule MinLength="2" ErrorMessage="Fel!" />
      </Binding.ValidationRules>
   </Binding>
</TextBox>

Slutligen, om jag vill använda kod för att detektera att inmatningen inte validerar så finns det flera metoder. Den enklaste är att använda kontrollens funktion GetBindingExpression som ger ett objekt som helt enkelt har en property som heter HasError.

var binding = textBox.GetBindingExpression(TextBox.TextProperty);
if (binding.HasError)
   return;

Detta kan göras t.ex. när användaren klickar OK.

Categories: General



En kopp kaffe!

Bjud mig på en kopp kaffe (20:-) som tack för bra innehåll!

Bjud på en kopp kaffe!

Om...

Kontaktuppgifter, med mera, finns här.

Följ mig

Twitter Instagram
GitHub RSS

Public Service

Folkbildning om public service.

Hem   |   linktr.ee/hesselbom   |   winsoft.se   |   80tal.se   |   Filmtips