27 september 2011

Gratis afbeeldingen geleverd door Visual Studio

In de installatiedirectory van Visual Studio zit een .zip-bestand met daarin een boel afbeeldingen (icons) die je kan gebruiken voor je eigen programma’s. Het is zelfs toegelaten om die iconen te gebruiken voor commerciële programma’s.

Op mijn machine (Windows 7, 64 bit en Visual Studio 2010) vond ik in de map C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\VS2010ImageLibrary\1033 het bestand VS2010ImageLibrary.zip met daarin de afbeeldingen.

19 september 2011

Entiteiten van naam veranderen

Als je een methode, variabele, parameter, … van naam wil veranderen kan je natuurlijk een “search & replace”-actie over je bestanden doen. Vaak is het gemakkelijker om via rechtsklik > Refactor > Rename de naamsverandering door te voeren.

refactor

Omzetten naar gehele getallen

Er zijn meerder methoden om een double getal om te zetten naar een geheel getal. De cast-operator realiseert een getal door afkapping, terwijl Convert.ToInt32 afrondt naar het dichtsbijzijnde geheel getal.

Bijvoorbeeld:

double d = 5.6;
int a = (int)d; // a = 5
int b = Convert.ToInt32(d); // b = 6

Bij de afronding maakt men gebruik van het zogenaamde Bankiersalgoritme. Dit betekent dat, als een getal halfweg ligt tussen twee gehele getallen, je afrondt naar het dichtstbijzijnde even getal. Dus 4.5 wordt 4, en 5.5 wordt 6.

Wil je “gewoon” afronden, dien je te werken met de methode Math.Round waarbij je met een extra parameter instelt welk soort afronding je wenst.

13 september 2011

Key events op een Form

Volgend programma demonstreert hoe je kan detecteren of en welke van de pijltjestoetsen ingedrukt en weer losgelaten worden.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_KeyDown(object sender, KeyEventArgs e)
    {
        HandleKeyEvent(e, "down");
    }

    private void Form1_KeyUp(object sender, KeyEventArgs e)
    {
        HandleKeyEvent(e, "released");
    }

    private void HandleKeyEvent(KeyEventArgs e, string state)
    {
        if (e.KeyCode == Keys.Up)
            infoLabel.Text = "Arrow up is " + state;
        if (e.KeyCode == Keys.Down)
            infoLabel.Text = "Arrow down is " + state;
        if (e.KeyCode == Keys.Left)
            infoLabel.Text = "Arrow left is " + state;
        if (e.KeyCode == Keys.Right)
            infoLabel.Text = "Arrow right is " + state;
    }
}

De events die ons hier interesseren zijn KeyDown (toets ingedrukt) en KeyUp (toets weer losgelaten). Door de property KeyCode te vergelijken met één van de voorgedefinieerde constanten in de klasse Keys, kan je exact bepalen om welke toets het gaat.

Dit geeft als uitvoer bijvoorbeeld:

KeyEvents

Bemerk ook het gebruik van een methode HandleKeyEvent om zoveel mogelijk dubbele code te vermijden.

12 september 2011

Drag & Drop in .NET programma’s

Gebaseerd op dit artikel ga ik je het meest eenvoudige programma uit de doeken doen dat drag & drop ondersteunt. De applicatie toont een leeg formulier waarop je één (of meerdere) bestanden kan “droppen”. Bijvoorbeeld vanaf de desktop of de verkenner, zoals hieronder aangegeven.

DNDTest1

Hier sleep je 3 bestanden en 1 directory naar het formulier door eerst met CTRL die items aan te klikken en vervolgens te slepen naar de centrale zone. Als je de muis vervolgens loslaat, verschijnt een bericht met alle bestands- en mapnamen:

DNDTest2

Om te beginnen start je een leeg project. De control die de drop-operaties ontvangt, moet dit toelaten: vandaar de property AllowDrop die je op true zet:

public Form1()
{
    InitializeComponent();

    AllowDrop = true;
}

Nu moet je twee events afhandelen: DragEnter en DragDrop. Het eerste event (DragEnter) wordt afgevuurd zodra de gebruiker met de muis het formulier binnengaat. Hiermee kan je de muiscursorafbeelding wijzigen indien de operatie toegelaten is:

private void Form1_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        e.Effect = DragDropEffects.All;
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}

De if-test (met e.Data.GetDataPresent) controleert of er wel bestanden gedropt worden. Indien dat het geval is, stel je de property e.Effect op de waarde DragDropEffects.All. Hierdoor verschijnt het bekende plusteken bij de muiscursor. In het andere geval is draggen verboden en wordt de muiscursor daaraan aangepast. Sleep bijvoorbeeld de prullenbak eens naar het formulier en je zal merken dat dit niet lukt.

Het tweede event bevat de eigenlijke gegevens (in dit geval de bestandsnamen) en handel je als volgt af:

private void Form1_DragDrop(object sender, DragEventArgs e)
{
    string[] files = (string[])e.Data.GetData(DataFormats.FileDrop, false);

    string message = String.Join("\r\n", files);
    MessageBox.Show(message);
}

Eerst zet je de data om naar een string-array, hiervoor is een cast-operatie noodzakelijk. Vervolgens formatteer je deze array tot één string en toon je die in een berichtvenster.

Download het volledige programma.

7 september 2011

Tekenen op componenten en het Paint-event

Typisch (en voor de eenvoud) wordt het maken van tekeningen in .NET als volgt aangeleerd:

  • Zet een PictureBox control op een Form
  • In een event handler (bv. een Button) maak je een Graphics object aan via deze PictureBox (met de methode CreateGraphics)
  • Teken met dit Graphics object

Dit stukje code illustreert deze aanpak:

private void drawButton_Click(object sender, EventArgs e)
{
    Graphics g = redPictureBox.CreateGraphics();
    Pen pen = new Pen(Color.Black);
    int diameter = redPictureBox.Width - 4;
    g.DrawEllipse(pen, 2, 2, diameter, diameter);
}

Dit ziet er dan zo uit:

badpicturebox

Om beginnende programmeurs snel grafische leuke toepassingen te laten maken is dit perfect, maar toch zijn er een aantal ernstige tekortkomingen:

  1. De PictureBox wordt niet bijgewerkt, bijvoorbeeld bij het minimaliseren en herstellen van de applicatie
  2. Het Graphics object g wordt niet opgeruimd achter de schermen. Volgens de MSDN documentatie moet je ervoor zorgen dat een aanroep van CreateGraphics later gevolgd wordt door een Dispose van het aangemaakte object
  3. Threading issues: de teken-code wordt uitgevoerd in de thread die verantwoordelijk is voor eventhandling. Er is echter een aparte thread die ervoor zorgt dat alle controls netjes getekend en up-to-date worden gehouden. Je mag in feite deze twee zaken niet mengen
  4. Verkeerd gebruik van PictureBox. De klassenaam zegt het zelf: een Picture Box dient om afbeeldingen te tonen en niet om allerhande lijnen en vormen op te tekenen. Hiervoor gebruik je volgens deze referentie beter een Panel.

We gaan nu deze tekortkomingen één voor één wegwerken door ons programma aan te passen.

Up-to-date houden van de PictureBox (of een Component in het algemeen)

We passen het programma als volgt aan:

private bool clicked = false;

public Form2()
{
    InitializeComponent();
}

private void drawButton_Click(object sender, EventArgs e)
{
    clicked = true;
    redPictureBox.Invalidate();
}

private void redPictureBox_Paint(object sender, PaintEventArgs e)
{
    if (clicked)
    {
        Graphics g = e.Graphics;
        Pen pen = new Pen(Color.Black);
        int diameter = redPictureBox.Width - 4;
        g.DrawEllipse(pen, 2, 2, diameter, diameter);
    }
}

Elke component (dus ook een PictureBox) wordt op gezette tijden ververst. Het besturingssysteem vraagt dan om, bijvoorbeeld na het minimaliseren en herstellen van een venster, om de componenten opnieuw te tekenen. Je kan hierop inspelen door het Paint-even af te handelen. In ons voorbeeld doen we dit als volgt:

private void redPictureBox_Paint(object sender, PaintEventArgs e)

Merk op dat we nu niet meer het Graphics object aanmaken via CreateGraphics, maar dat je het Graphics object kan verkrijgen als property van het PaintEventArgs-object e. Meer nog: CreateGraphics werkt niet in deze methode!

In de event handler van de Button geven we een signaal dat de klik heeft plaatsgevonden en dat de cirkel op de PictureBox mag worden getekend. Via Invalidate zorg je ervoor dat het Paint-event zal afgevuurd worden.

Threading issues

Als gevolg van deze aanpassing is er geen “teken”-code die uitgevoerd wordt in de event-thread:

  • Het Paint-event wordt afgehandeld in de thread die verantwoordelijk is voor het up-to-date houden van componenten
  • Invalidate wordt uitgevoerd in de event-thread, maar vraagt (onrechtstreeks) dat het Paint-event wordt afgevuurd zodat de cirkel kan worden getekend.

Panel in plaats van PictureBox

Het is een eenvoudige oefening om het voorbeeld om te bouwen. Verwijder de PictureBox en vervang door een Panel component.

Referenties: