mercoledì 31 dicembre 2014

ImageData con il canvas

Ho un canvas che misura 600 x 600 pixel.
Ora creo un imageData.
<!DOCTYPE html>
<html>
<head>
<script>
var canvas;
var ctx;
var x;
var y;
var alfa;
var sfondo = new Image();

function inizia(){
 canvas=document.getElementById("myCanvas");
 ctx=canvas.getContext("2d");
 ctx.fillStyle="#FFFFCC";
 ctx.rect(0,0,600,600);
 ctx.fill();
 elabora();
}
function elabora(){
 
 var imgData = ctx.createImageData(100, 1);

 for (var i = 0; i < 100; i+=4) {
     imgData.data[i]=255;
     imgData.data[i+1]=0;
     imgData.data[i+2]=0;
     imgData.data[i+3]=255;
 }

 ctx.putImageData(imgData, 10, 10);
}

window.addEventListener("load", inizia);
</script>
</head>
<body>

 <canvas id="myCanvas" width="600" height="600">
 </canvas>
</body>
</html> 
Ho creato un'immagine di dimensioni 100 x 1;
Definisco i valori dei pixel ogni 4: rosso, verde, blu, trasparenza;
Metto l'immagine creata alle coordinate 10,10.

Ed ecco che viene fuori una striscia rossa orizzontale.

Come primo passo andiamo bene.

lunedì 29 dicembre 2014

Scrivere un file XML con il C#

Ho trovato il modo di scrivere un file XML direttamente da un programma fatto con iln C#.
private void button1_Click(object sender, EventArgs e)
        {
            XmlTextWriter writer = new XmlTextWriter("C:/Users/Antonello/product.xml", System.Text.Encoding.UTF8);
            writer.WriteStartDocument(true);
            writer.Formatting = Formatting.Indented;
            writer.Indentation = 2;
            writer.WriteStartElement("Table");
            createNode("1", "Product 1", "1000", writer);
            createNode("2", "Product 2", "2000", writer);
            createNode("3", "Product 3", "3000", writer);
            createNode("4", "Product 4", "4000", writer);
            writer.WriteEndElement();
            writer.WriteEndDocument();
            writer.Close();
            MessageBox.Show("XML File created ! ");
        }

        private void createNode(string pID, string pName, string pPrice, XmlTextWriter writer)
        {
            writer.WriteStartElement("Product");
            writer.WriteStartElement("Product_id");
            writer.WriteString(pID);
            writer.WriteEndElement();
            writer.WriteStartElement("Product_name");
            writer.WriteString(pName);
            writer.WriteEndElement();
            writer.WriteStartElement("Product_price");
            writer.WriteString(pPrice);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
Il file può essere creato dovunque.
Resta da studiare la cosa e da elaborare anche un programma per la lettura.

domenica 28 dicembre 2014

Una linea in movimento mediante il canvas

Ecco, ho cominciato a penetrare i segreti dell'animazione mediante il canvas.
Il meccanismo è banale.
Quel tutorial che avevo trovato è un tantinello cervellotico, ma mi è servito a capire il meccanismo, che poi ho verificato su un tutorial più facile da capire.
Ecco il codice:
<!DOCTYPE html>
<html>
<head>
<script>
var canvas;
var ctx;
var x;
var y;
var sfondo = new Image();

function inizia(){
 canvas=document.getElementById("myCanvas");
 ctx=canvas.getContext("2d");
 ctx.fillStyle="#000080";
 ctx.beginPath();
 ctx.rect(0,0,600,600);
 ctx.closePath();
 ctx.fill();
 sfondo=ctx.getImageData(0,0,600,600);
 x=0;
 y=600;

 z=setInterval(disegna,100);
}

function disegna(){
 ctx.putImageData(sfondo,0,0);
 ctx.strokeStyle="white";
 ctx.beginPath();
 ctx.moveTo(0,0);
 ctx.lineTo(x,y);
 ctx.closePath();
 ctx.stroke();
 x=x+10;
}

window.addEventListener("load", inizia);
</script>
</head>
<body>

 <canvas id="myCanvas" width="600" height="600">
 </canvas>
</body>
</html> 
che traccia una linea che si muove.

Game su canvas...

Ora il tutorial fa delle aggiunte.
Provo a marcarle in rosso...

<!DOCTYPE html>
<html>
<head>
<script>
var canvas;
var ctx;

//memorizziamo le immagini
var back = new Image();
var oldBack=new Image();
var ship=new Image();

//memorizziamo le coordinate
var shipX=0;
var shipY=0;
var oldShipX=0;
var oldShipY=0;

function canvasSpaceGame(){
 canvas=document.getElementById("myCanvas");
 
 ctx=canvas.getContext("2d");
 
 
 ctx.fillStyle="black";
 ctx.rect(0,0,300,300);
 ctx.fill();
 
 back=ctx.getImageData(0,0,30,30);
 
 stars();  
 
 makeShip();   
 
 
        gameLoop = setInterval(doGameLoop, 16);

        
        window.addEventListener('keydown', whatKey, true);
 
}

function stars(){
 for(var i=0;i<50;i++){
  x=Math.floor(Math.random()*299);
  y=Math.floor(Math.random()*299);
  
  ctx.fillStyle="yellow"
  if (x<30 || y<30) ctx.fillStyle="black";
  ctx.beginPath();
  ctx.arc(x,y,3,Math.PI*2,false);
  ctx.closePath();
  ctx.fill(); 
  
  oldBack=ctx.getImageData(0,0,30,30); 
 }
}
function makeShip() {

       
        ctx.beginPath();
        ctx.moveTo(28.4, 16.9);
        ctx.bezierCurveTo(28.4, 19.7, 22.9, 22.0, 16.0, 22.0);
        ctx.bezierCurveTo(9.1, 22.0, 3.6, 19.7, 3.6, 16.9);
        ctx.bezierCurveTo(3.6, 14.1, 9.1, 11.8, 16.0, 11.8);
        ctx.bezierCurveTo(22.9, 11.8, 28.4, 14.1, 28.4, 16.9);
        ctx.closePath();
        ctx.fillStyle = "rgb(222, 103, 0)";
        ctx.fill();

        
        ctx.beginPath();
        ctx.moveTo(22.3, 12.0);
        ctx.bezierCurveTo(22.3, 13.3, 19.4, 14.3, 15.9, 14.3);
        ctx.bezierCurveTo(12.4, 14.3, 9.6, 13.3, 9.6, 12.0);
        ctx.bezierCurveTo(9.6, 10.8, 12.4, 9.7, 15.9, 9.7);
        ctx.bezierCurveTo(19.4, 9.7, 22.3, 10.8, 22.3, 12.0);
        ctx.closePath();
        ctx.fillStyle = "rgb(51, 190, 0)";
        ctx.fill();
        
        ship=ctx.getImageData(0,0,30,30);
        ctx.putImageData(oldBack, 0, 0);
}
  

window.addEventListener("load",function(){canvasSpaceGame()});
</script>
</head>
<body>
 <canvas id="myCanvas" width="300" height="300">
 </canvas>
</body>
</html> 
Ho marcato in verde le variabili globali, in rosso le istruzioni che memorizzano in una variabile delle immagini o le rimettono sul canvas (a quanto ho capito adesso) e in blu due righe che poco ancora capisco, di cui una aggiunge un evento da tastiera.
Ho notato che putImageData, da quanto riesco a capire, rimette sullo schermo il contenuto di immagine immagazzinato in una variabile precedentemente, e infatti rimettendo alle coordinate 0 e 0 oldBack nasconde l'astronave...

Cielo stellato e variazioni.

Seguiamo pedissequamente...

C'era il fatto di disegnare 50 stelle casuali.
Bene, adesso ho fatto un po' di variazioni e ho disegnato un cielo stellato più ampio, con stelle gialle.
<!DOCTYPE html>
<html>
<head>
<script>
function canvasSpaceGame(){
	canvas=document.getElementById("myCanvas");
	
	ctx=canvas.getContext("2d");
	
	
	ctx.fillStyle="black";
	ctx.rect(0,0,1000,600);
	ctx.fill();
	
	stars();     
}

function stars(){
	for(var i=0;i<6000;i++){
		x=Math.floor(Math.random()*999);
		y=Math.floor(Math.random()*999);
		
		ctx.fillStyle="yellow"
		
		ctx.beginPath();
		ctx.arc(x,y,1,Math.PI*2,false);
		ctx.closePath();
		ctx.fill();
	}
}
		

window.addEventListener("load",function(){canvasSpaceGame()});
</script>
</head>
<body>
	<canvas id="myCanvas" width="1000" height="600">
	</canvas>
</body>
</html> 
Come posso fare per fare alcune stelle gialle e altre bianche?
<!DOCTYPE html>
<html>
<head>
<script>
function canvasSpaceGame(){
	canvas=document.getElementById("myCanvas");
	
	ctx=canvas.getContext("2d");
	
	
	ctx.fillStyle="black";
	ctx.rect(0,0,1000,600);
	ctx.fill();
	
	stars();     
}

function stars(){
	for(var i=0;i<3000;i++){
		x=Math.floor(Math.random()*999);
		y=Math.floor(Math.random()*999);
		
		ctx.fillStyle="yellow"
		
		ctx.beginPath();
		ctx.arc(x,y,1,Math.PI*2,false);
		ctx.closePath();
		ctx.fill();
		
		x=Math.floor(Math.random()*999);
		y=Math.floor(Math.random()*999);
		
		ctx.fillStyle="white"
		
		ctx.beginPath();
		ctx.arc(x,y,1,Math.PI*2,false);
		ctx.closePath();
		ctx.fill();
	}
	
}
		

window.addEventListener("load",function(){canvasSpaceGame()});
</script>
</head>
<body>
	<canvas id="myCanvas" width="1000" height="600">
	</canvas>
</body>
</html> 

fillStyle, rect e fill nel canvas

Preso possesso del canvas, adesso vediamo che ci posso fare...

<!DOCTYPE html>
<html>
<head>
<script>
function canvasSpaceGame(){
	canvas=document.getElementById("myCanvas");
	
	ctx=canvas.getContext("2d");
	
	
	ctx.fillStyle="#AAFFBB";
	ctx.rect(0,0,300,300);
	ctx.fill();
}


window.addEventListener("load",function(){canvasSpaceGame()});
</script>
</head>
<body>
	<canvas id="myCanvas" width="300" height="300">
	</canvas>
</body>
</html> 
Ecco, ho definito il colore di riempimento, delineato un rettangolo e dato il comando fill che riempie il rettangolo in questione...

Iniziamo col canvas: prendiamone possesso dal Javascript...

Scriviamo Tutorial di riferimento il codice di un canvas, con tutti gli elementi del Path per tracciare "roba" su di esso...

<!DOCTYPE html>
<html>
<head>
<script>
</script>
</head>
<body>
 <canvas id="myCanvas" width="300" height="300">
 </canvas>
</body>
</html> 
Ora con il Javascript "prendiamo possesso" del canvas...

Inizio con la prima funzione Javascript "associata" all'evento load della finestra.
<!DOCTYPE html>
<html>
<head>
<script>
function inizia(){
 
}


window.addEventListener("load",function(){inizia()});
</script>
</head>
<body>
 <canvas id="myCanvas" width="300" height="300">
 </canvas>
</body>
</html> 
E ora "prendiamo possesso" del canvas.

<!DOCTYPE html>
<html>
<head>
<script>
function canvasSpaceGame(){
 canvas=document.getElementById("myCanvas");
 
 ctx=canvas.getContext("2d");
 
}


window.addEventListener("load",function(){canvasSpaceGame()});
</script>
</head>
<body>
 <canvas id="myCanvas" width="300" height="300">
 </canvas>
</body>
</html> 

sabato 27 dicembre 2014

Ereditarietà in Javascript

Creo un costruttore qualunque:
<script>
function classe(nome){
 this.chiama=function(){
  alert(nome);
 }
}
function inizia(){
 var oggetto=new classe("Cicciobello");
 oggetto.chiama();
}
window.addEventListener("load",function(){inizia()});
</script>
Bene.
Ho creato un'istanza oggetto di una classe classe.

Ora voglio creare una classe che eredita da classe...

Ecco:
<script>
function classe(nome){
 this.chiama=function(){
  alert(nome);
 }
}

function figlia(nome){
 classe.call(this,nome);
}
function inizia(){
 var oggetto=new classe("Cicciobello");
 oggetto.chiama();
 
 var derivata=new figlia("Mario");
 derivata.chiama();
}
window.addEventListener("load",function(){inizia()});
</script>
E ottengo una seconda MessageBox con il nome "Mario". Quindi funziona.

Come controprova tolgo la riga con scritto classe.call(this,nome);
<script>
function classe(nome){
 this.chiama=function(){
  alert(nome);
 }
}

function figlia(nome){
 
}
function inizia(){
 var oggetto=new classe("Cicciobello");
 oggetto.chiama();
 
 var derivata=new figlia("Mario");
 derivata.chiama();
}
window.addEventListener("load",function(){inizia()});
</script>
E vediamo:
Sì, non ottengo più la seconda MessageBox.
Questo che significa?
Praticamente invece di scrivere public classe o :classe o extends classe come nei vari linguaggi, qui si inserisce il nome della classe genitrice con il call(this.... e i parametri).

GameLoop nella creazione di giochi con l'uso del Canvas in HTML5

Adesso viene soltanto immessa la funzione GameLoop.
Proviamo...

<!DOCTYPE html>
<html>
<head>
<script>
function Game(){
 
 this.div=document.getElementById("GameDiv");
 this.div.style.width="768";
 this.div.style.height="512";
 
 this.canvas=document.getElementById("GameCanvas");
 this.canvas.setAttribute("width","768");
 this.canvas.setAttribute("height","512");
 this.canvas.defaultWidth=this.canvas.width;
 this.canvas.defaultHeight=this.canvas.height;
 
 this.canvas.style.cursor="none";
 
 this.ctx=this.canvas.getContext("2d");
 
 this.GameLoop=function(){
  
  if(!this.paused){
   this.Update();
  }
  this.Draw();
  window.requestAnimFrame(function(){
   game.GameLoop();
  });
 }
  
}

function StartGame(){
 game=new Game();
}
window.addEventListener("load",function(){
 StartGame();
},true);
</script>
</head>
<body>
 <div id="GameDiv">
  <canvas id="GameCanvas">
  </canvas>
 </div>
</body>
</html>
Però ora il tutorial mi fa una divagazione sull'ereditarietà prototipale del Javascript...
Conviene seguirla...

Creazione di games con HTML5: parte iniziale (scrittura del costruttore di Game)

Mettiamo un DIV.
<!DOCTYPE html>
<html>
<head>
<script>
<!DOCTYPE html>
<html>
<head>
<script>

 
</script>
</head>
<body>
 <div id="GameDiv">
  <canvas id="GameCanvas">
  </canvas>
 </div>
</body>
</html>>


Ora scriviamo la funzione Game() in Javascript.
<!DOCTYPE html>
<html>
<head>
<script>
function Game(){
 
 this.div=document.getElementById("GameDiv");
 this.div.style.width="768";
 this.div.style.height="512";
 
 this.canvas=document.getElementById("GameCanvas");
 this.canvas.setAttribute("width","768");
 this.canvas.setAttribute("height","512");
 this.canvas.defaultWidth=this.canvas.width;
 this.canvas.defaultHeight=this.canvas.height;
 
 this.canvas.style.cursor="none";
 
 this.ctx=this.canvas.getContext("2d");
}

function StartGame(){
 game=new Game();
}
window.addEventListener("load",function(){
 StartGame();
},true);
</script>
</head>
<body>
 <div id="GameDiv">
  <canvas id="GameCanvas">
  </canvas>
 </div>
</body>
</html>


Ecco.

venerdì 26 dicembre 2014

Disegnare triangoli in Direct3D senza VertexBuffer

Disegno triangoli soltanto definendo un array di vertici.
Questo è il codice che definisce i vertici.
public void CreaVertici()
        {
            vertices = new CustomVertex.PositionColored[9];
            vertices[0].Position = new Vector3(0f, 0f, 0f);
            vertices[0].Color = Color.Blue.ToArgb();
            vertices[1].Position = new Vector3(0f, 10f, 0f);
            vertices[1].Color = Color.Blue.ToArgb();
            vertices[2].Position = new Vector3(10f, 0f, 0f);
            vertices[2].Color = Color.Blue.ToArgb();
            vertices[3].Position = new Vector3(5f, 0f, 0f);
            vertices[3].Color = Color.Green.ToArgb();
            vertices[4].Position = new Vector3(5f, 15f, 0f);
            vertices[4].Color = Color.Green.ToArgb();
            vertices[5].Position = new Vector3(15f, 0f, 0f);
            vertices[5].Color = Color.Green.ToArgb();
            vertices[6].Position = new Vector3(0f, -10f, 0f);
            vertices[6].Color = Color.Red.ToArgb();
            vertices[7].Position = new Vector3(18f, -15f, 0f);
            vertices[7].Color = Color.Red.ToArgb();
            vertices[8].Position = new Vector3(15f, 8f, 0f);
            vertices[8].Color = Color.Red.ToArgb();
        }
Il numero evidenziato in rosso su giallo è il numero di vertici compresi nell'array,

Se non si usa vertexBuffer bisogna specificare i vertici uno dopo l'altro anche se sono uguali.
Una volta specificati i vertici, di tipo CustomVertex.PositionColored, si usa il comando che li stampa:
        private void render()
        {
            device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 1.0f, 0);
            device.BeginScene();
            device.VertexFormat = CustomVertex.PositionColored.Format;
            device.DrawUserPrimitives(PrimitiveType.TriangleList, 3, vertices);
            device.EndScene();
            device.Present();
        }
Ecco, quello marcato in rosso è il comando fondamentale, che stampa i triangoli specificando:
  • Il tipo di primitiva
  • Il numero di primitive
  • l'array di vertici da usare.
Il numero di primitive è importante! Se non si specifica il numero giusto non vengono disegnate tutte le primitive.
Ecco l'immagine:



Ma se io uso device.DrawUserPrimitives specificando un numero inferiore di primitive:
        private void render()
        {
            device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 1.0f, 0);
            device.BeginScene();
            device.VertexFormat = CustomVertex.PositionColored.Format;
            device.DrawUserPrimitives(PrimitiveType.TriangleList, 2, vertices);
            device.EndScene();
            device.Present();
        }
Ottengo questa immagine:

mercoledì 24 dicembre 2014

Costruttori in Javascript

Cerchiamo di riprendere il Javascript...

Se quello che scriviamo abitualmente è solo il costruttore di una classe...
function funzione()
{
 alert("Ciao");
}

funzione();
ho verificato mediante il C# se sia ammissibile dichiarare una variabile nel corso di un metodo
        private void metodo()
        {
            int numero = 0;
            numero = 1234;
            Console.WriteLine(numero);
            Console.ReadLine();
        }
e lo è: è una variabile locale, allo stesso modo di come viene dichiarata nel costruttore in Javascript:
function funzione()
{
 var numero=0;
 numero=1234;
 alert(numero);
}

domenica 21 dicembre 2014

DirectX

Codice che ho ricavato finora:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;

namespace DirectXProject
{
    public partial class mioForm : Form

    {
        
        public Device device;
        private float angle = 0f;
        CustomVertex.PositionColored[] vertices;
       
        public mioForm()
        {
            InitializeComponent();
            
        }

        public void InitializeDevice()
        {
            
            PresentParameters presentParams = new PresentParameters();
            presentParams.BackBufferCount = 1;
            presentParams.BackBufferFormat = Manager.Adapters[0].CurrentDisplayMode.Format;
            presentParams.BackBufferWidth =1366;
            presentParams.BackBufferHeight =768;
            
            presentParams.Windowed = false;
            presentParams.FullScreenRefreshRateInHz = Manager.Adapters[0].CurrentDisplayMode.RefreshRate;
            presentParams.SwapEffect = SwapEffect.Discard;

            device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
        }

        public void CreaVertici()
        {
            vertices= new CustomVertex.PositionColored[3];
            vertices[0].Position = new Vector3(0f, 0f, 0f);
            vertices[0].Color = Color.Red.ToArgb();
            vertices[1].Position = new Vector3(0f, 10f, 0f);
            vertices[1].Color = Color.Red.ToArgb();
            vertices[2].Position = new Vector3(10f, 0f, 0f);
            vertices[2].Color = Color.Red.ToArgb();
           
        }
        public void Camera()
        {
            device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1f, 100f);
             
            device.Transform.View = Matrix.LookAtLH(new Vector3(0, 0, -50), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            device.RenderState.Lighting = false;
            device.RenderState.CullMode = Cull.None;
        }

        private void render()
        {
            device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 1.0f, 0);
            device.BeginScene();
            device.VertexFormat = CustomVertex.PositionColored.Format;
            device.Transform.World = Matrix.RotationX(angle) * Matrix.RotationZ(angle);
            device.DrawUserPrimitives(PrimitiveType.TriangleList,1,vertices);
            device.EndScene();
            device.Present();
            angle += 0.1f;

        }
       
        public static void Main()
        {


                mioForm frm = new mioForm();
                frm.WindowState = FormWindowState.Maximized;
                frm.InitializeDevice();
                frm.CreaVertici();
                frm.Camera();
                frm.Show();
                while (frm.Created)
                {
                    frm.render();
                    Application.DoEvents();
                }
            

        }
        

        private void mioForm_Load(object sender, EventArgs e)
        {

        }

        private void mioForm_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
        
    }
}
A parte l'effetto ameno di un triangolo rosso che ruota in modo un po' pazzoide, cerchiamo di analizzare il codice...

Nel costruttore viene chiamata solo InitializeComponent, che si trova nell'altro mezzo modulo con la mezza classe form...

Poi ho marcato in colori diversi i metodi fondamentali dell'oggetto che mi servono.
Sono quattro:
  • Uno che crea il Device
  • Uno che crea i Vertici
  • Uno che setta la Telecamera
  • Uno che dà l'avvio al disegno.
Così dovrebbe essere più chiaro.
I quattro vengono richiamati nel metodo Main()

domenica 14 dicembre 2014

Esercizio elementare di moltiplicazione di matrici relative alle coordinate di un punto.

Dunque proviamo a moltiplicare le due semplici matrici:
x         2  0
y         0  2
Le dimensiohni sono (2 x 1) e (2 x 2).
Dobbiamo invertirle:
2  0      x  
0  2      y
Adesso abbiamo (2 x 2) e (2 x 1), quindi sono moltiplicabili e ne verrà fuori una matrice di dimensioni (2 x 1).
2x + 0y = 2x
0x + 2y = 2y
La matrice risultante sarà quindi
2x
2y
Ed ecco che il valore raddoppia.

Moltiplicazione fra matrici

Le dimensioni delle matrici si descriverebbero con il numero di righe seguito dal numero di colonne.
 2  1 -1            1  1  2  2
-2  3  4            2 -3  1  3
                   -1 -1  5  2


Le dimensioni della prima matrice sono (2 x 3) e (3 x 4).
I numeri interni sono uguali, e quindi le due matrici sono moltiplicabili.
La matrice prodotto sarà uguale a (2 x 4).
Ogni riga si moltiplica per ogni colonna.
2*1 + 1*2 + (-1)*(-1) = 2 + 2 + 1 = 5;

2*1 + 1*(-3)+ (-1)*(-1) = 2 -3 +1 = 0;

2*2 + 1*1 + (-1)*5 = 4 + 1 -5 = 0;

2*2 + 1*3 + (-1)*2 = 4 +3 -2 = 5:


(-2)*1 + 3*2 + 4*(-1) = -2 + 6 -4 = 0;

(-2)*1 + 3*(-3) + 4*(-1) = -2 -9 -4 = -15;

(-2)*2 + 3*1 + 4*5 = -4 +3 +20 = 19;

(-2)*2 + 3*3 + 4*2 = -4 +9 +8 = 13;


Risultato:
5   0   0   5
0 -15  19  13
Corrisponde all'esempio trovato.
Prendiamo in esame questa proprietà del codice che serve per la telecamera:
device.Transform.View = Matrix.LookAtLH(new Vector3(0, 0, -40), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
Adesso modifico il primo parametro, che dovrebbe essere il punto in cui io mi pongo con la telecamera per osservare il triangolo.
Come lo modifico, riporto anche l'ìmmagine, con pazienza, per ricostruire come mi sposto.


Partiamo dalla base.
device.Transform.View = Matrix.LookAtLH(new Vector3(0, 0, -40), new Vector3(0, 0, 0), new Vector3(0, 1, 0));

sabato 13 dicembre 2014

Studio di uno scheletro generale per un'applicazione DirectX

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;

namespace DirectXProject
{
    public partial class mioForm : Form

    {
        public Device device;
        CustomVertex.PositionColored[] vertices;

        public mioForm()
        {
            InitializeComponent();
            
        }

        public void InitializeDevice()
        {
            angle = 0f;
            PresentParameters presentParams = new PresentParameters();
            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;

            device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
        }

        public void CreaVertici()
        {
            vertices= new CustomVertex.PositionColored[3];
            vertices[0].Position = new Vector3(0f, 0f, 0f);
            vertices[0].Color = Color.Red.ToArgb();
            vertices[1].Position = new Vector3(10f, 0f, 0f);
            vertices[1].Color = Color.Green.ToArgb();
            vertices[2].Position = new Vector3(5f, 10f, 0f);
            vertices[2].Color = Color.Yellow.ToArgb();    
        }

        public void Camera()
        {
            device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1f, 50f);

            device.Transform.View = Matrix.LookAtLH(new Vector3(0, 0, -30), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
            device.RenderState.Lighting = false;
            device.RenderState.CullMode = Cull.None;
        }


        protected override void OnPaint(PaintEventArgs e)
        { 
            device.Clear(ClearFlags.Target, Color.DarkSlateBlue, 1.0f, 0);
            device.BeginScene();
            device.VertexFormat = CustomVertex.PositionColored.Format;
            device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, vertices);
            device.EndScene();
            device.Present();
        } 
        
       
        public static void Main()
        {
            mioForm frm = new mioForm();
            frm.InitializeDevice();
            frm.CreaVertici();
            frm.Camera();
            Application.Run(frm);

        } 
    }
}
Dunque il metodo CreaVertici(), che ho creato io, non fa altro che inizializzare un array di vertici del tipo PositionColored.
Il metodo Camera(), sempre creato da me, non fa che posizionare la telecamera, il punto di osservazione.

Il metodo Main chiama questi metodi che preparano i vertici e la telecamera, e quindi inizia il metodon OnPaint, nel quale:
  1. Si dice al device qual è il formato dei vertici.
  2. Si dice quale primitiva usare e la matrice di vertici da prendere in considerazione.

lunedì 8 dicembre 2014

Gestori di eventi e delegati.

Nell'ambito di una classe è dichiarato l'evento, con il nome preceduto dall'handler.
    class altraClasse
    {
        public event mioHandler Evento;

    }
che ancora mi dà errore perché quel mioHandler non significa ancora nulla.

Nell'ambito della stessa classe viene dichiarato il delegato, ossia l'handler.
Cerchiamo di interpretare con parole "umane": l'evento viene dichiarato con il nome dell'handler.
L'handler, essendo dichiarato come delegato, esprime la sua firma, ossia il numero e il tipo di parametri. Questo significa che quel dato evento può essere gestito solo da funzioni che abbiano quella firma.
Quindi con la dichiarazione del delegato noi non facciamo altro che esprimere il fatto che quel gestore di eventi, che gestisce quel dato evento, può essere espressione soltanto di funzioni con una determinata firma.
Proviamo:
    class altraClasse
    {
        public event mioHandler Evento;

        public delegate void mioHandler();

    }
Ecco, il mio handler, o delegato, non ha parametri.
Più semplice di così...

Ora, però, mettiamo nella nostra classe una funzione che evochi l'evento...
    class altraClasse
    {
        public event mioHandler Evento;

        public delegate void mioHandler();

        public void Evoca()
        {
            Evento();
        }

    }
Bene.

Ora dalla classe principale proviamo a istanziare questa classe.
        public static void Main()
        {
            altraClasse cs = new altraClasse();
            
        }
Ora, dopo che nel contesto dell'altra classe è stato stabilito che l'evento va gestito da una funzione avente la firma definita dall'handler (delegato), in questa classe si sostituisce al delegato una funzione esistente che abbia la stessa firma, ossia Scrivi().
        public static void Main()
        {
            altraClasse cs = new altraClasse();
            cs.Evento += new altraClasse.mioHandler(Scrivi);
            cs.Evoca();
        }
Quindi si evoca l'evento, e funziona.

Bene. Dunque nella classe che esprime l'evento si esprime, tramite il delegato/handler, il tipo di funzione che dovrà gestire l'evento. Punto.
Poi, in un'altra classe, si può verificare l'evento associando all'evento una funzione che abbia la giusta firma.

Spero di aver capito...

domenica 7 dicembre 2014

Array di controlli in C#

Ho realizzato un array di controlli.
Ecco il codice:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace Project10
{
    class mioForm:Form
    {
        Button[] bottone;
        public mioForm()
        {
            InitializeComponent();
        }

        public static void Main()
        {
            mioForm frm = new mioForm();
            Application.Run(frm);
            
           
        }
        
        private void InitializeComponent()
        {
           
            bottone = new Button[23];
            this.SuspendLayout();
            for (int n = 0; n < 23; n++)
            {
                bottone[n] = new Button();
                bottone[n].Width = 50;
                bottone[n].Height = 50;
                bottone[n].Left = bottone[n].Width * (n % 5);
                bottone[n].Top = bottone[n].Height * (n / 5);
                bottone[n].Text = (n+1).ToString();
                this.Controls.Add(bottone[n]);
               
            }

            
            this.ClientSize = new System.Drawing.Size(584, 561);
            
            this.Name = "mioForm";
            this.Load += new System.EventHandler(this.mioForm_Load);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        private void mioForm_Load(object sender, EventArgs e)
        {
           
        }

       

        
    }

}
Resta ancora da vedere come gestire l'evento Click di ciascuno dei controlli, altrimenti che ci faccio?
Per il momento, passo ad altro.

Creazione dal nulla di un'applicazione con un form in C#

Apro un progetto vuoto.
Ci aggiungo i riferimenti (System.Windows.Forms) e li collego al programma;
aggiungo una classe mioForm.cs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Project10
{
    class mioForm
    {
    }
}
Adesso faccio ereditare la mia classe dalla classe Form.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Project10
{
    class mioForm:Form
    {

    }
}
Aggiungo il metodo statico Main();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Project10
{
    class mioForm:Form
    {


        public static void Main()
        {

        }
    }
}
Facendo partire questo, ottengo solo una fugace visualizzazione di una console.

Ora dichiaro e istanzio la mia classe derivata da Form, e faccio partire l'applicazione con il mio oggetto:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Project10
{
    class mioForm:Form
    {


        public static void Main()
        {
            mioForm frm = new mioForm();
            Application.Run(frm);
        }
    }
}
Ottengo una console e un form, che resta lì e devo chiudere con la "crocetta" in alto a destra per terminare l'esecuzione.

Ecco che mi viene fuori un codice generato automaticamente:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace Project10
{
    class mioForm:Form
    {


        public static void Main()
        {
            mioForm frm = new mioForm();
            Application.Run(frm);
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // mioForm
            // 
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Name = "mioForm";
            this.Load += new System.EventHandler(this.mioForm_Load);
            this.ResumeLayout(false);

        }

        private void mioForm_Load(object sender, EventArgs e)
        {

        }
    }
}
In più mi viene fuori un errore relativo al Drawing:
Errore 1 Il tipo 'System.Drawing.Size' è definito in un assembly di cui manca il riferimento. Aggiungere un riferimento all'assembly 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. c:\users\antonello\documents\visual studio 2010\Projects\Project10\Project10\mioForm.cs 24 13 Project10
Errore 2 Il tipo o il nome dello spazio dei nomi 'Drawing' non esiste nello spazio dei nomi 'System'; probabilmente manca un riferimento a un assembly c:\users\antonello\documents\visual studio 2010\Projects\Project10\Project10\mioForm.cs 24 42 Project10
Introduco il riferimento a Drawing...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
Adesso funziona di nuovo.
Bene.
Il codice che si genera automaticamente una volta che viene fatto "partire" il form è:
        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // mioForm
            // 
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Name = "mioForm";
            this.Load += new System.EventHandler(this.mioForm_Load);
            this.ResumeLayout(false);

        }

        private void mioForm_Load(object sender, EventArgs e)
        {

        }
ossia il codice che inizializza il Form.
Però questo codice non viene eseguito all'atto della creazione del form.
Provo a cambiare il tipo di applicazione da "Applicazione Console" a "Applicazione WindowsForm" e ugualmente noto che il codice non viene eseguito, in quanto se cambio i parametri non risultano modificati.
Cambio le dimensioni del form:
        private void InitializeComponent()
        {
            this.SuspendLayout();
            
            // 
            // mioForm
            // 
            this.ClientSize = new System.Drawing.Size(584, 261);
            this.Name = "mioForm";
            this.Load += new System.EventHandler(this.mioForm_Load);
            this.ResumeLayout(false);

        }

        private void mioForm_Load(object sender, EventArgs e)
        {
           
        }
e il form mi viene sempre delle dimensioni standard.
Dunque devo ricreare il costruttore del form e metterci dentro la chiamata a InitializeComponent():
    class mioForm:Form
    {

        public mioForm()
        {
            InitializeComponent();
        }

        public static void Main()
        {
            mioForm frm = new mioForm();
            Application.Run(frm);
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            
            // 
            // mioForm
            // 
            this.ClientSize = new System.Drawing.Size(584, 261);
            this.Name = "mioForm";
            this.Load += new System.EventHandler(this.mioForm_Load);
            this.ResumeLayout(false);

        }

        private void mioForm_Load(object sender, EventArgs e)
        {
           
        }

        
    }
e adesso il form mi viene delle nuove dimensioni!


sabato 6 dicembre 2014

Pagina web di riferimento

Il device è quello che dà accesso diretto all'adattatore grafico.
Diretto accesso all'hardware.
Ovviamente, il device va dichiarato.
E su questo non abbiamo problemi.
namespace WindowsFormsApplication12
{
    public partial class Form1 : Form
    {
        private Device device;

        public Form1()
        {
            InitializeComponent();
            
        }

        static void Main()
        {
            
            Application.Run(new Form1());
        }
        private void Form1_Load(object sender, EventArgs e)
        {
        
        }     
    }
}
Bene.
Una volta dichiarato, come per tutte le variabili, bisogna istanziarlo.
Quindi sarà d'obbligo usare qualcosa tipo device = new Device(....).
Vediamo come.
Credo che sia il caso di istanziarlo nel costruttore.
mmmhhh... mi sa che qui viene istanziato nel metodo Main...
Ma andiamo per gradi.
Il costruttore del Device richiede come parametri PresentationParameters, e quindi viene creato un metodo InitializeDevice che setta i parametri e inizializza il device.
        public void InitializeDevice()
        {
            PresentParameters presentparams = new PresentParameters();
            presentparams.Windowed = true;
            presentparams.SwapEffect = SwapEffect.Discard;

            device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentparams);
        }
Ecco, i parametri vanno inizializzati soltanto relativamente ad "applicazione a finestra", ossia non fullscreen, e a "scartare lo swapeffect", che a quanto ho capito consiste nello scrivere nel dispositivo mediante un buffer che viene presentato a runtime.

Quindi il costruttore del device richiede una serie di parametri, che magari vedo successivamente.
Per il momento mi basta aver dichiarato e istanziato il device con questi due pezzi di codice: la dichiarazione nel contesto delle variabili del form e l'istanziazione (inizializzazione) nel contesto del metodo InitializeDevice().
Ora inserisco il richiamo al metodo InitializeDevice() nel metodo Main() e poi avvio, e funziona.
C'è solo un form, uno stupidissimo form del tutto normale. Però dovrebbe essere stato creato un device.
Mi fido sulla parola!

venerdì 5 dicembre 2014

Ora apro un progetto di tipo Windows Form.
Se ci metto una Main come metodo nel form mi crea casini perché dice che è definito più di un punto di ingresso.
Perché?
Ma ecco: cosa ti vedo? Io creo un nuovo progetto di tipo Windows Form e oltre alla classe Form.cs mi si crea automaticamente una classe Program.cs, in cui è comunque contenuto un metodo Main.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication12
{
    static class Program
    {
        /// 
        /// Punto di ingresso principale dell'applicazione.
        /// 
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
E' qui che sta la Main!
E' questo, il punto di ingresso!
Sperimentiamoci sopra...

    static class Program
    {
        /// 
        /// Punto di ingresso principale dell'applicazione.
        /// 
        [STAThread]
        static void Main()
        {
            MessageBox.Show("Ciao, bello");
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
Ecco, così facendo prima di mostrarmi il Form mi appare la MessageBox con il messaggio "Ciao, bello".
Da notare che il metodo Main è statico, e anche la classe è statica.
Adesso faccio diversamente e metto il Main nel contesto del Form.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

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

        static void Main()
        {
            
            Application.Run(new Form1());
        }
        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
}
E si apre regolarmente.
Però per far questo devo eliminare l'altro metodo Main che fa parte della classe statica Program.cs.

mercoledì 3 dicembre 2014

Studio dei rudimenti delle classi in C#

Ho aperto un'applicazione tipo Windows Form.
Ecco cosa mi trovo:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

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

        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
}
Ora, anziché fare iniziare il programma dal Form, voglio farlo iniziare con una Main.
Come fare? Credo che qui si possano solo creare classi...

Ecco, la cosa mi è stata risolta in parte automaticamente.
Se ho una classe da cui far partire il programma, essa dovrà essere static ed essere messa in un modulo a parte che abbia lo stesso nome della classe.
Ecco il modulo inizio.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;

namespace WindowsFormsApplication9
{
    static class Program
    {

        static void Main()
        {
            MessageBox.Show("Ciao mondo crudele");
            Form1 ciccio=new Form1();
            ciccio.BackColor = Color.Green;
            Application.Run(ciccio);

        }
    }
}
E questo è il modulo del Form1, Form1.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

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

        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }

    public class inizio
    {
        void Main()
        {
            MessageBox.Show("Ciao mondo crudele");
        }
    }
}

Il metodo Main della classe inizio.cs istanzia un oggetto di tipo Form1, lo colora di verde e lo mostra dopo aver esibito una MessageBox:
        static void Main()
        {
            MessageBox.Show("Ciao mondo crudele");
            Form1 ciccio=new Form1();
            ciccio.BackColor = Color.Green;
            Application.Run(ciccio);

        }
    }
Cominciamo a "quagliare"...

Brancolando nel buio con il DirectX senza un'adeguata preparazione sul C#

Credo di aver trovato un tutorial piuttosto valido...

Cominciamo a seguirlo, tentando di ricordare e rafforzare il mio approccio di apprendimento...




Per prima cosa, stando al tutorial, devo aprire un progetto di C# tipo Window...
Proviamo...
Fatto.

Ora aggiungiamo i riferimenti.
Fatto.
Inseriamo le linee di codice per l'uso delle librerie referenziate.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

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

        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
}
Bene.
Adesso il tutorial dice di variare le dimensioni del form e il suo nome... giusto a titolo di esercizio.
Facciamolo.
Si fa dal metodo InitializeComponents(). Ora, InitializeComponents viene chiamato dal costruttore del form, ma non è presente sul modulo principale del form, bensì su quello chiamato Designer,dove c'è l'altra parte della partial class del Form...
Lo apro e ce lo trovo...
namespace DirectX
{
    partial class Form1
    {
        /// 
        /// Variabile di progettazione necessaria.
        /// 
        private System.ComponentModel.IContainer components = null;

        /// 
        /// Liberare le risorse in uso.
        /// 
        /// ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Codice generato da Progettazione Windows Form

        /// 
        /// Metodo necessario per il supporto della finestra di progettazione. Non modificare
        /// il contenuto del metodo con l'editor di codice.
        /// 
        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);

        }

        #endregion
    }
}
Provo a modificare, in questo metodo, i parametri.
        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(500, 500);
            this.Name = "Form1";
            this.Text = "Form del cavolo";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);

        }
Nessun problema...

Adesso mi dice di modificare la Main...

Bene, ho problemi perché avendo aperto un'applicazione tipo Windows non ho una Main...

Retrocediamo e studiamo un po' come si può avere un inizio da una Main che istanzia un form, anziché da un form stesso.

lunedì 1 dicembre 2014

Primi passi con il device (DirectX) in C#

Diamo una definizione di polimorfismo.
Un oggetto di un tipo può assumere valori di un tipo derivato dal suo tipo.
Un parametro di un certo tipo può essere sostituito da un parametro di un tipo derivato.

Dunque quando nel metodo OnPaint di un oggetto di classe derivata da form si mette la parola chiave override questo significa che questo metodo overridda (scavalca) quello della classe base.

Questo OnPaint è un metodo, non un evento.

Ma adesso trovo un codice diverso che non fa uso di OnPaint...

Il nucleo di questo codice è:
        static void Main()
        {
            MioForm frm = new MioForm();
            frm.initGraphics();

            frm.Show();

            while(frm.Created){
            frm.Render();
            Application.DoEvents();
            }
            
        }

La prima riga del metodo non fa altro che istanziare un nuovo frm di classe MioForm la quale è una classe derivata da Form.
Quindi chiama initGraphics che è il metodo nel quale viene creato il device.
        void initGraphics()
        {
            PresentParameters pp = new PresentParameters();
            pp.Windowed = true;
            pp.SwapEffect = SwapEffect.Discard;

            dispositivo = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, pp);
        }
Quindi si mostra il form, semplicemente:
frm.Show();
e quindi si usa frm.Created.
Che roba è?
Mentre il controllo esiste, si eseguono le successive istruzioni comprese fra parentesi graffe...
Se provo a sostituire while con if, il form sparisce, probabilmente perché la condizione di esistenza del form viene verificata solo una volta.
            while(frm.Created){
            frm.Render();
            Application.DoEvents();
            }


Quindi riassumendo inizialmente si crea il device, quindi si mostra il form e mentre il form esiste si esegue il metodo Render, che "aziona" il device evocandone dei metodi che portano al mostrare il form con certe caratteristiche...

Cominciamo a decifrare...

sabato 29 novembre 2014

Creazione del device (DirectX): si inizia brancolando nel buio...

Dichiariamo un device.

    class MioForm:Form
    {
        private Device dispositivo=null;

        static void Main()
        {
            MioForm frm = new MioForm();
            Application.Run(frm);

        }
        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // MioForm
            // 
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Name = "MioForm";
            this.Load += new System.EventHandler(this.MioForm_Load);
            this.ResumeLayout(false);

        }

        private void MioForm_Load(object sender, EventArgs e)
        {

        }
    }
}
Ora dovrei creare un metodo InitGraphics.
        void initGraphics()
        {
            PresentParameters pp = new PresentParameters();
            pp.Windowed = true;
            pp.SwapEffect = SwapEffect.Discard;

            dispositivo = new Device(0,DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, pp);
        }
In questo metodo viene creato il dispositivo. Per la creazione del dispositivo, è necessario inserire alcuni parametri, fra i quali PresentParameters, che viene quindi modificato prima della creazione.

Non so ancora niente sul significato di queste cose, che approfondirò successivamente.

Obiettivo: studiare i parametri della creazione del device.
Cosa rappresentano i parametri del costruttore del device?
Dunque... Inizio a parlare per tentativi, mettendo a punto quel poco che ho capito in attesa di chiarificazione (in omaggio al principio che quando i bambini iniziano a parlare non studiano l'analisi logica e grammaticale).
Il primo parametro è un numero che fa riferimento al device fisico. E' uno in meno del numero dei devices.
Il secondo parametro è il tipo di device, che può essere hardware o software (arabo puro... almeno per il momento)
Il terzo parametro identifica la finestra da collegare al device, che in questo caso è this nel senso che è il qui presente form
Il quarto parametro è il tipo di comportamento delle Flags (per me al momento è arabo antico purissimo... ma non disperiamo)
Il quinto parametro sono i parametri di presentazione che abbiamo definito poco fa.


Bene.
Dunque mi pare di intravvedere una qualche logica pur nella nebulosità della situazione.
Prima si identifica questo fantomatico dispositivo fisico, quindi si specifica il tipo di dispositivo (non fisico? Virtuale?...) che si vuole e si fa il collegamento del dispositivo alla finestra.
Successivamente si definiscono le flags (?) e i parametri di presentazione (?).

Ho detto sicuramente un sacco di scemenze, ma ogni cultura inizia dalla scemenza, in fondo...

venerdì 28 novembre 2014

Visualizzazione di percentuali sugli istogrammi in pila 100% in Excel.

Per far figurare le percentuali in un istogramma in pila su Excel.

Età > 65 641 727 636 694 635 483  
Età <=65 99 109 104 109 151 54  
Queste sono le righe di Excel, riga 3 e riga 4, per le quali ho costruito, prendendone i dati, due istogrammi in pila, uno con valori assoluti e uno in percentuale.
Voglio che le etichette dati facciano figurare non i valori assoluti ma i valori in percentuale.
Dunque costruisco l'istogramma in pila partendo da valori in percentuale, e non da questi qui che invece sono i valori assoluti.

Predispongo due righe, e le chiamo Età > 65 percentuale e Età <=65 percentuale.

Nella cella corrispondente alla prima della riga con Età > 65 metto la formula: =B3/(B3+B4)*100

Nella cella corrispondente alla prima della riga con Età <=65 metto la formula: =B4/(B3+B4)*100

Trascino le formule su tutte le righe e ottengo:
Età > 65 percentuale 86,62162162 86,96172249 85,89951378 86,42590286 80,78880407 89,94413408  
Età <=65 percentuale 13,37837838 13,03827751 14,10048622 13,57409714 19,21119593 10,05586592  
Mostrando le etichette dati come "Valore" ottengo la rappresentazione, ovviamente, di queste percentuali.

giovedì 27 novembre 2014

Grafici di Excel: problemi di etichette.

L'altezza dei valori di un istogramma in Excel è troppo differente, perché ho valori di 30.000 accanto a valori di 100, per cui la visualizzazione dei valori più bassi è scarsissima.
Che fare?

Fare grafici separati, è l'unica cosa.
O dividere opportunamente i valori in modo da farli venire più vicini ai valori meno grandi.

Adesso studiamo del grafico come far apparire e sparire le etichette sugli assi dei grafici.

Cliccando lontano dal grafico, sull'area stessa del grafico, ottengo un menu "lungo", nel quale appare "Opzioni grafico".
Apro questo menu e apro la seconda scheda "Assi".
Ora, al posto dei numeri progressivi, voglio far apparire l'anno.
Come faccio?

Torno a quel menu "lungo", e vado su "Dati di origine", quindi su "Etichette asse categorie" si seleziona la parte del grafico che contiene i nomi da far apparire nelle ascisse.

lunedì 24 novembre 2014

Funzioni che scompongono un numero con gli slash e lettera finale

Convertire la lettera dopo lo slash in numero.
Ecco due funzioni:
Function sinistra(s As String) As Integer
    Dim numero As Integer
    If InStr(s, "/") <> 0 Then
        numero = InStr(s, "/") - 1
    Else
        numero = Len(s)
    End If
    sinistra = CInt(Left(s, numero))
End Function
Isola il numero precedente lo slash.

Per l'altra ci devo ragionare.
Se non c'è slash, InStr è pari a zero, e quindi il valore della funzione destra deve essere zero.
Eccola:
Function destra(s As String) As Integer
    If InStr(s, "/") <> 0 Then
        destra = Asc(Right(s, Len(s) - InStr(s, "/")))
    Else
        destra = 0
    End If
End Function

Funzione Asc(String) per ricavare il codice ascii di un carattere.

Adesso troviamo il modo di ricavare il codice ASCII di un carattere...

Facile:
Sub ascii()
    MsgBox Asc("A")
End Sub
che restituisce 65.
Ricordo che il codice ASCII esadecimale di A era 41H, che appunto corrisponde a 65 decimale.

domenica 23 novembre 2014

Codice per individuare il numero di protocollo più alto fra le varie occorrenze di una singola persona nel database.

...e ho buttato giù il codice per selezionare il numero di protocollo più alto fra le varie occorrenze di una stessa persona.
Ecco il foglio con un ipotetico numero di protocollo sulla colonna A:



Ed ecco il codice:
Sub main()
    Dim indirizzo As String
    Dim numero As Long
    Set c = Cells.Find("antonio cacchioni")
    If c Is Nothing = False Then
        indirizzo = c.Address
        Do
        Set c = Cells.FindNext(c)
        
        If Cells(c.Row, c.Column + 1).Formula = "56" And Cells(c.Row, c.Column - 1).Formula > numero _
            Then numero = Cells(c.Row, c.Column - 1).Formula
        Loop While c.Address <> indirizzo
    End If
    MsgBox numero
End Sub
Risultato:



Okay! Funziona!

Ripasso metodi Find e FindNext di un oggetto Range in Excel

Ecco... riprendo confidenza con i metodi Find e FindNext dell'oggetto Range.

Mi creo un foglio di lavoro con dei nomi di fantasia e delle età...



Ed ecco il codice:
Sub main()
    Dim indirizzo As String
    Set c = Cells.Find("antonio cacchioni")
    If c Is Nothing = False Then
        indirizzo = c.Address
        Do
        Set c = Cells.FindNext(c)
        c.Interior.Color = vbCyan
        Loop While c.Address <> indirizzo
    End If
End Sub
Il FindNext, una volta trovato l'ultimo indirizzo, ricomincia daccapo.
Per questo è importante interrompere il Loop quando l'indirizzo del range trovato coincide con l'indirizzo iniziale.

Ed ecco il codice eseguito, con la colorazione in celeste di tutte le occorrenze del nome prescelto:

martedì 18 novembre 2014

Risolto problema dell'amministrativo della ASL per i compensi orari, con il formato delle celle tipo "ora".

La formula che ho inserito nella cella "Compenso orario" è =F5*60, ossia moltiplica per 60 il valore contenuto nella cella F5.
Così, se in F5 ho 1.5 il risultato nella cella "Compenso orario" è 90.
Se in F5 ho 2, il risultato della cella "Compenso orario" è 120.

Lapalissiano!

Il problema è se il formato di F5 è formato ora e non generico

Ho conferito a F5 il formato "ora", e se inserisco 1:00, nella casella Compenso Orario ottengo adesso 2,50.
Perché?

Proviamo ancora con altri valori...

Con 2:00 ottengo 5,00.
Con 3:00 ottengo 7,50 (me l'aspettavo!)

Perché? Mi sembra ovvio che la cella Compenso Orario non moltiplichi 60 per 1, 2 o 3, ma per altri numeri.
Riconvertendo il formato di F5 con il numero scritto dentro, vediamo a cosa corrisponde quel numero in formato generico...

1:00 ---> 0,0416666666666667
2:00 ---> 0,0833333333333333
3:00 ---> 0,125
Googliamo con questi valori.


Nella cella in formato Ora è rappresentato il numero di ore, ma in realtà il valore della cella è pari al numero di giorni che entrano in quel numero di ore, ossia numero di ore / 24.
Quando si moltiplica dunque, nella cella Compenso Orario 60 per il valore della cella, non lo si moltiplica per il numero di ore rappresentato, ma per il numero di giorni compreso in quelle ore.
Per ottenere il valore reale, bisogna moltiplicare per 24 il prodotto di 60 per il valore della cella.

sabato 15 novembre 2014

OpenGL, cmake, primi passi.

Che accidenti è CMake?
L'ho scaricato e ora lo sto decomprimendo.

Per intanto mi vado a fumare una sigaretta...
Non so come manipolarlo avendo scaricato la versione compressa... Fammi un po' scaricare l'exe...

Bene. Adesso si sta installando, pare...

PATH too long???

Boh? Semmai ce lo installo io poi...

Ecco, installato col collegamento al desktop.
Ecco quello che mi appare, adesso:



...e vediamo come accidenti si usa!

venerdì 7 novembre 2014

Il programma di mio figlio Daniele

Daniele sta costruendo un programma in Java.
Ecco il codice:
package supercraftdemo;

import java.applet.*;

public class Component extends Applet implements runnable{
    private static final long serialVersionUID = 1L;
    private static int pixelSize = 2;
    public static Dimension size = new Dimension(700,560);
    public static Dimension pixel = new Dimension(size.width / pixelSize, size.height / pixelSize);
    
    public static String name = "supercraft";
    
    public static boolean isRunning = false;
    
    private image screen;
    
    public static Level level;
    
 public Component(){
     setPreferedSize(size);
     

 }

 public void start(){
  //definire oggetti
  level = new Level();
  
  //inizio loop del gioco
  isrunning = true;
  new trhead(this).start();
 }
 
 public void stop (){
  isRunning = false;
 }
 
 public static void main(String args []) {
               Component  component = new Component();
               
               JFrame = new JFrame();
}

giovedì 6 novembre 2014

Esercizio sui nomi dei campi in Excel con VBA

Codice per definire i nomi dei campi in Excel con VBA(esercizio):
Sub main()
Cells.Select
Selection.Interior.Color = vbWhite
On Error Resume Next
ActiveWorkbook.Names("mioCampo").Delete
num = 1 + Int(Rnd() * 10)
num2 = 1 + Int(Rnd() * 10)
ActiveWorkbook.Names.Add Name:="mioCampo", RefersTo:=Range(Cells(1, 1), Cells(num2, num))
Range("miocampo").Interior.Color = vbRed
Range("A1").Select
End Sub

mercoledì 15 ottobre 2014

Arrotondamenti in VBA Excel

Me ne sono andato in crisi profondissima di fronte al modo in cui VBA Excel opera gli arrotondamenti.

Poi, andando in crisi, non riesco più a ragionare lucidamente, e non riuscendo a ragionare lucidamente me ne vado ancora più in crisi, fin quando collasso completamente e mi ci sento male da un punto di vista emotivo.
Meglio sempre razionalizzare per iscritto, altrimenti mi imprigiono dentro queste "strutture emotive patologiche" che mi paralizzano del tutto.

In fondo, è per questo che ho deciso di tenere un diario, solo che poi non lo compilo perché ho paura di essere troppo prolisso su argomenti banali e di appesantirlo.


Ecco.
Quello che mi serve è approssimare per difetto le frazioni da 0,1 a 0,4 e per eccesso quelle da 0.5 a 0.9.
Aiutandomi con spunti che ho trovato in rete, ecco la conclusione cui sono arrivato.
Mi creo un codice apposta per sperimentare la cosa.

Sub main()
    Dim n As Double
    For n = 1 To 5 Step 0.1
        Debug.Print n & " -----> " & Int(n + 0.5)
    Next n
End Sub
1 -----> 1
1,1 -----> 1
1,2 -----> 1
1,3 -----> 1
1,4 -----> 1
1,5 -----> 2
1,6 -----> 2
1,7 -----> 2
1,8 -----> 2
1,9 -----> 2
2 -----> 2
2,1 -----> 2
2,2 -----> 2
2,3 -----> 2
2,4 -----> 2
2,5 -----> 3
2,6 -----> 3
2,7 -----> 3
2,8 -----> 3
2,9 -----> 3
3 -----> 3
3,1 -----> 3
3,2 -----> 3
3,3 -----> 3
3,4 -----> 3
3,5 -----> 4
3,6 -----> 4
3,7 -----> 4
3,8 -----> 4
3,9 -----> 4
4 -----> 4
4,1 -----> 4
4,2 -----> 4
4,3 -----> 4
4,4 -----> 4
4,5 -----> 5
4,6 -----> 5
4,7 -----> 5
4,8 -----> 5
4,9 -----> 5
5 -----> 5
Sembra che risponda egregiamente ai miei piani. Era così banale, ma nel circolo vizioso in cui mi ero messo non ci avevo pensato: sono stato spiazzato dal metodo apparentemente assurdo di approssimazione che Excel opera, convertendo 0.5 ora nell'intero superiore ora in quello inferiore a seconda che la parte intera sia pari o dispari (Banker, pare che si chiami, ossia metodo "del banchiere"...)

Adesso applichiamolo alla bisogna.

Io ho un numero di turni da attribuire a due reparti che hanno rispettivamente un numero variabile di medici.
Faccio prima senza approssimazione:
Sub main()
    Dim turni As Integer
    Dim medCar As Integer, medMed As Integer
    Dim turniCar As Double, turniMed As Double
    Dim turniPerMedico As Double
    
    turni = 15
    medCar = 4
    medMed = 5
    
    turniPerMedico = turni / (medCar + medMed)
    
    turniCar = turniPerMedico * medCar
    turniMed = turniPerMedico * medMed
    
    Debug.Print turniCar
    Debug.Print turniMed
End Sub
 6,66666666666667 
 8,33333333333333 


Vario le cifre:
    medCar = 5
    medMed = 5
 7,5 
 7,5 



    medCar = 6
    medMed = 5
 8,18181818181818 
 6,81818181818182 


Adesso introduciamo un arrotondamento come quello che ho ricavato.

Sub main()
    Dim turni As Integer
    Dim medCar As Integer, medMed As Integer
    Dim turniCar As Double, turniMed As Double
    Dim turniPerMedico As Double
    
    turni = 15
    medCar = 4
    medMed = 5
    
    turniPerMedico = turni / (medCar + medMed)
    
    turniCar = Int(turniPerMedico * medCar + 0.5)
    turniMed = Int(turniPerMedico * medMed + 0.5)
    
    Debug.Print turniCar
    Debug.Print turniMed
End Sub
 7 
 8 



    medCar = 5
    medMed = 5
 8 
 8 



    medCar = 6
    medMed = 5
 8 
 7 


Facciamo un confronto con i risultati che avevo ottenuto senza approssimazione:
  1. Primo caso: 4 e 5:
    Senza arrotondamento:
     6,66666666666667 
     8,33333333333333 
    
    Con arrotondamento:
     7 
     8 
    somma: 15
    
    
  2. Secondo caso: 5 e 5:
    Senza arrotondamento:
     7,5 
     7,5 
    
    Con arrotondamento:
     8 
     8 
    somma:16
    
  3. Terzo caso: 6 e 5:
    Senza arrotondamento:
     8,18181818181818 
     6,81818181818182
    
    Con arrotondamento:
     8 
     7 
    somma:15
    


Solo nel caso in cui i due gruppi siano uguali, dal momento che la cifra decimale è 0.5, viene arrotondata in alto in ambedue, "creando" un turno in più: in tutti gli altri casi, essendo le cifre decimali diverse e necessariamente una al di sopra di 0.5 e l'altra al di sotto, i turni vengono divisi bene con l'arrotondamento, venendo attribuiti al gruppo cui ne "spetterebbe" la parte maggiore.


Dunque dal momento che l'unica situazione in cui la somma dei turni per reparto non corrisponde al totale dei turni pare sia questa, bisogna trovare un modo per normalizzare la cosa.

martedì 7 ottobre 2014

Arrotondamento dei turni teorici.

Ecco il piccolo codice che mi sono costruito per studiare le approssimazioni per difetto e per eccesso:
Sub main()
Dim cifraDouble As Double
Dim cifraInteger As Integer
cifraDouble = 1.4
cifraInteger = cifraDouble

Debug.Print ""
Debug.Print "Il valore della variabile Double è " & cifraDouble
Debug.Print "Valore della variabile double copiato in una variabile Integer " & cifraInteger
Debug.Print "Uso di Int " & Int(cifraDouble)
Debug.Print "Uso di CInt " & CInt(cifraDouble)
End Sub
Cambiando il valore di cifraDouble ottengo nella finestra immediata il valore ottenuto ponendo il valore in una variabile di tipo Integer o usando rispettivamente Int e CInt.
Il valore della variabile Double è 1,4
Valore della variabile double copiato in una variabile Integer 1
Uso di Int 1
Uso di CInt 1

Il valore della variabile Double è 1,5
Valore della variabile double copiato in una variabile Integer 2
Uso di Int 1
Uso di CInt 2

Il valore della variabile Double è 1,9
Valore della variabile double copiato in una variabile Integer 2
Uso di Int 1
Uso di CInt 2

Usare la variabile di tipo Integer o la parola chiave CInt ottiene gli stessi risultati, ossia arrotonda una cifra decimale fino a 0,5 di decimali all'intero inferiore, mentre la arrotonda se ha 0,5 o più di decimali all'intero superiore.
Invece Int arrotonda sempre per difetto, all'intero inferiore.
Per il mio programma sarà più conveniente usare CInt o porre il valore in una variabile di tipo Integer.


Resta il problema se c'è un numero uguale di elementi nei due gruppi.
Torno a considerare la cosa:
turni attribuiti card 17
turni attribuiti med 18
turni teorici card 18
turni teorici med 18
Ecco! Dal momento che ambedue le cifre in questo caso hanno 0.5 di decimali il numero viene arrotondato per eccesso in ambedue i turni, e si ha la "creazione" di un turno in più che non dovrebbe esserci.

Come risolvere la cosa?

La soluzione più giusta sarebbe risolvere la cosa a caso, togliendo casualmente un turno a uno dei due.
Dim turniTot As Integer
Dim medCar, medMed As Integer
Dim turniCar As Integer, turniMed As Integer
turniTot = k - 1 + f
medCar = 6
medMed = 6

turniCar = turniTot / (medCar + medMed) * medCar
turniMed = turniTot / (medCar + medMed) * medMed

If turniCar + turniMed > turniTot Then
    Dim estratto As Integer
    estratto = Int(Rnd() * 2)
    If estratto = 0 Then turniCar = turniCar - 1
    If estratto = 1 Then turniMed = turniMed - 1
End If
        
Debug.Print "turni teorici card " & turniCar
Debug.Print "turni teorici med " & turniMed
Così andiamo pure bene... ma diamo un'occhiata in diverse "estrazioni", nella finestra immediata, ai turni attribuiti e a quelli teorici ottenuti sottraendo a caso al numero falsamente gonfiato un turno:
turni attribuiti card 18
turni attribuiti med 17
turni teorici card 18
turni teorici med 17
turni attribuiti card 17
turni attribuiti med 18
turni teorici card 17
turni teorici med 18
turni attribuiti card 17
turni attribuiti med 18
turni teorici card 18
turni teorici med 17
...e mi rendo conto che una distribuzione "equa" era già stata fatta, in quanto il primo turno del mese era stato estratto casualmente mentre gli altri venivano in rigorosa alternanza.
Dunque non è il caso di andare avanti in elucubrazioni cervellotiche.
Basta fare che se il numero dei due gruppi è uguale non è necessario operare una correzione, e le cosa sono già sistemate da sé.

Calcolo dei turni teorici per reparto.

Ora devo conoscere, per fare i calcoli opportuni sulla scrittura dei turni alternati come ho fatto finora:
  • il numero totale dei turni disponibili
  • il numero dei medici di ciascun reparto
turniTot è il numero di turni totali disponibili.
medCar è il numero di medici della cardiologia.
medMed è il numero di medici della medicina.
turniCar è il numero di turni della cardiologia.
turniMed è il numero di turni della medicina.
turniTot / (medCar + medMed) = numero dei turni per ciascun medico.
Moltiplicando il numero di turni per ciascun medico per il numero di medici di ciascun reparto ottengo il numero di turni per ciascun reparto.

turniCar = turniTot / (medCar + medMed) * medCar

turniMed = turniTot / (medCar + medMed) * medMed
Calcoliamoli...
Sub main()
    Dim d As Date, nomeFoglio As String
    
    d = DateAdd("m", 1, Date)      'attribuisce alla variabile locale d il valore del mese successivo alla data attuale
    nomeFoglio = MonthName(Month(d)) & " " & Year(d)    'attribuisce alla stringa nomeFoglio mese e anno della variabile locale d
    
    Sheets.Add          'aggiunge un nuovo foglio
    On Error GoTo x     'gestisce l'errore di tentata rinominazione del foglio
    ActiveSheet.Name = nomeFoglio   'rinomina il foglio con la stringa nomeFoglio.
    ActiveSheet.Move after:=Sheets(Sheets.Count) 'sposta alla fine della cartella il nuovo foglio

        
    formattaFoglio Month(d), Year(d)  'formatta il foglio secondo lo schema
    attribuzioneTurni
    Exit Sub
x:     'gestione dell'errore di tentata rinominazione del foglio: elimina il foglio senza suscitare avvisi di eliminazione.
    Application.DisplayAlerts = False
    ActiveSheet.Delete
    Application.DisplayAlerts = True
End Sub

Private Sub attribuzioneTurni()
Dim reparti(1) As String
Dim n As Integer
Dim f As Integer
Dim tCar, tMed As Integer

reparti(0) = "CAR"
reparti(1) = "MED"
 
n = Int(Rnd() * 2)
Range("Reparto").Cells(1, 1).Formula = reparti(n)
If reparti(n) = "CAR" Then
    tCar = tCar + 1
Else
    tMed = tMed + 1
End If

For k = 2 To Range("Reparto").Rows.Count
    If n = 0 Then
        n = 1
    Else
        n = 0
    End If
    Range("Reparto").Cells(k, 1).Formula = reparti(n)
    If reparti(n) = "CAR" Then
        tCar = tCar + 1
    Else
        tMed = tMed + 1
    End If
    
    If festivo(Range("Reparto").Cells(k, 1).Offset(0, -2).Formula) Then
        If Range("Reparto").Cells(k, 1).Formula = "MED" Then
            Range("Reparto").Cells(k, 1).Formula = "CAR - MED"
            tCar = tCar + 1
        Else
            Range("Reparto").Cells(k, 1).Formula = "MED - CAR"
            tMed = tMed + 1
        End If
        f = f + 1
    End If
Next k
Debug.Print "turni attribuiti card " & tCar
Debug.Print "turni attribuiti med " & tMed


Dim turniTot As Integer
Dim medCar, medMed As Integer
Dim turniCar, turniMed As Integer
turniTot = k - 1 + f
medCar = 7
medMed = 6

turniCar = turniTot / (medCar + medMed) * medCar
turniMed = turniTot / (medCar + medMed) * medMed
Debug.Print "turni teorici card " & turniCar
Debug.Print "turni teorici med " & turniMed
End Sub
Risultato
turni attribuiti card 18
turni attribuiti med 17
turni teorici card 18,8461538461538
turni teorici med 16

Ecco, questo è il calcolo completo (quello marcato in rosso), ma c'è qualcosa che non va: il fatto che i turni siano espressi in una cifra decimale, essendo indivisibili.
Per giunta, la somma non combina, essendo espresso uno solo in frazioni di turno.
Sicuramente non sono appropriate le variabili che ho usato per immagazzinarne il valore.
Rifeccio:
Dim turniTot As Integer
Dim medCar, medMed As Integer
Dim turniCar, turniMed As Double
turniTot = k - 1 + f
medCar = 7
medMed = 6

turniCar = turniTot / (medCar + medMed) * medCar
turniMed = turniTot / (medCar + medMed) * medMed
Debug.Print "turni teorici card " & turniCar
Debug.Print "turni teorici med " & turniMed
Proviamo:
turni attribuiti card 17
turni attribuiti med 18
turni teorici card 18,8461538461538
turni teorici med 16,1538461538462
Perfetto! Si tratta di numeri periodici, per cui la verifica non darà mai il risultato esatto, comunque la somma fra i due mi restituisce 34,99999999..... che si può considerare esatto! I turni totali sono infatti 35.
Adesso dobbiamo vedere come liberarci delle frazioni di turno...
Intanto devo correggere un errore di codice che ho commesso: il motivo per cui uno dei risultati era frazionario e l'altro no è stato che io ho dichiarato le variabili in questo modo:
Dim turniCar, turniMed As Integer
credendo, in quanto non ricordavo bene questo aspetto della sintassi del VBA, che anche turniCar sarebbe stata dichiarata come Integer, mentre invece in realtà questa veniva dichiarata come Variant, e quindi mi dava un risultato decimale.
Correggendo la cosa...
Dim turniCar As Integer, turniMed As Integer
ottengo:
turni attribuiti card 17
turni attribuiti med 18
turni teorici card 19
turni teorici med 16
La somma è sempre 35, ma il risultato viene arrotondato.

Sarebbe il caso di vedere come VBA arrotonda... Mi costruisco un programmino dedicato.

Scrittura dei turni in forma alternata con riserva di modifica successiva

Ma forse mi conviene alternare, come ho fatto prima, per poi sostituire alcuni turni per far quadrare il conto.

L'algoritmo era questo:
Dim reparti(1) As String
Dim n As Integer
Dim f As Integer

reparti(0) = "CAR"
reparti(1) = "MED"
 
n = Int(Rnd() * 2)
Range("Reparto").Cells(1, 1).Formula = reparti(n)

For k = 2 To Range("Reparto").Rows.Count
    If n = 0 Then
        n = 1
    Else
        n = 0
    End If
    Range("Reparto").Cells(k, 1).Formula = reparti(n)
    If festivo(Range("Reparto").Cells(k, 1).Offset(0, -2).Formula) Then
        If Range("Reparto").Cells(k, 1).Formula = "MED" Then
            Range("Reparto").Cells(k, 1).Formula = "CAR - MED"
        Else
            Range("Reparto").Cells(k, 1).Formula = "MED - CAR"
        End If
        f = f + 1
    End If
Next k
in cui predispongo un array di due elementi stringa, "CAR" e "MED", quindi estraggo casualmente uno dei due e lo metto nella prima cella:
reparti(0) = "CAR"
reparti(1) = "MED"
 
n = Int(Rnd() * 2)
Range("Reparto").Cells(1, 1).Formula = reparti(n)
Quindi alterno la disposizione successiva in questo modo:
For k = 2 To Range("Reparto").Rows.Count
    If n = 0 Then
        n = 1
    Else
        n = 0
    End If
    Range("Reparto").Cells(k, 1).Formula = reparti(n)
Next k
Ossia prendo alternativamente i due elementi dell'array, "CAR" e "MED".

Per i festivi uso questo:
If festivo(Range("Reparto").Cells(k, 1).Offset(0, -2).Formula) Then
        If Range("Reparto").Cells(k, 1).Formula = "MED" Then
            Range("Reparto").Cells(k, 1).Formula = "CAR - MED"
        Else
            Range("Reparto").Cells(k, 1).Formula = "MED - CAR"
        End If
        f = f + 1
    End If
mettendo il giorno a CAR se la notte è MED e viceversa.
In tutto questo, sapendo il numero teorico di turni per CAR e MED, posso inserire un codice che conti i vari CAR e MED in modo da operare poi le sostituzioni nel modo opportuno.
Proviamo...
Private Sub attribuzioneTurni()
    Dim reparti(1) As String
    Dim n As Integer
    Dim f As Integer
    Dim tCar, tMed As Integer

    reparti(0) = "CAR"
    reparti(1) = "MED"
 
     n = Int(Rnd() * 2)
     Range("Reparto").Cells(1, 1).Formula = reparti(n)
    If reparti(n) = "CAR" Then
        tCar = tCar + 1
    Else
        tMed = tMed + 1
    End If

    For k = 2 To Range("Reparto").Rows.Count
        If n = 0 Then
            n = 1
        Else
            n = 0
        End If
        Range("Reparto").Cells(k, 1).Formula = reparti(n)
        If reparti(n) = "CAR" Then
            tCar = tCar + 1
        Else
            tMed = tMed + 1
        End If
    
        If festivo(Range("Reparto").Cells(k, 1).Offset(0, -2).Formula) Then
            If Range("Reparto").Cells(k, 1).Formula = "MED" Then
                Range("Reparto").Cells(k, 1).Formula = "CAR - MED"
                tCar = tCar + 1
            Else
                Range("Reparto").Cells(k, 1).Formula = "MED - CAR"
                tMed = tMed + 1
            End If
            f = f + 1
        End If
    Next k
    Debug.Print "turni card " & tCar
    Debug.Print "turni med " & tMed

End Sub
Ecco: le aggiunte contrassegnate in rosso su giallo sono quelle che ho fatto per contare i turni CAR e i turni MED del mese.
Ho contrassegnato peraltro in bianco su blu una variabile che mi serve per contare il numero dei festivi e aggiungerlo al numero delle celle in modo da avere il totale dei turni disponibili.

Distribuzione casuale dei turni.

Questo è un modo di distribuire casualmente un numero num di contenuti di una cella in modo casuale all'interno di un range formato da numTot celle.
Sub main()
Dim num, numTot, contatore As Integer

    num = 13
    numTot = 30
    contatore = 0
    Do While contatore < num
        k = 1 + Int(Rnd() * numTot)
            If Range(Cells(1, 1), Cells(numTot, 1)).Cells(k, 1).Formula = "" Then
                Range(Cells(1, 1), Cells(numTot, 1)).Cells(k, 1).Formula = "CAR"
                contatore = contatore + 1
            End If
    Loop
End Sub
La distribuzione è assolutamente casuale.

lunedì 29 settembre 2014

Scrittura in rosso dei turni straordinari

Ora devo individuare come fare per catturare i multipli di tre, nei punteggi che figurano nella tabella.

Sembra di aver risolto...
Sub calcola()
    Dim x As Range
    Set x = Range("ListaNomiMese").Find(lastcaption)
    If x Is Nothing = False Then
        x.Offset(0, 2).Formula = x.Offset(0, 2).Formula - 1
        Straordinario x
    End If
    Set x = Range("ListaNomiMese").Find(Bersaglio.Formula)
    If x Is Nothing = False Then
        x.Offset(0, 2).Formula = x.Offset(0, 2).Formula + 1
        Straordinario x
    End If
End Sub

Private Sub Straordinario(x As Range)
    If x.Offset(0, 2).Formula Mod 4 = 0 Then
        colore = vbRed
    Else
        colore = vbBlack
    End If
    Bersaglio.Font.Color = colore
End Sub
Sì, funziona! Praticamente ogni tre turni ne scrive uno, sul Calendario, in rosso, a significare un turno straordinario.

La routine che calcola i turni.

Non ci perdiamo in nebulosità mentali, altrimenti non andiamo avanti!

Calcoliamo la quantità di turni fatti nel mese.
A quanto ho capito, non interessa sapere quanti turni di giorno o di notte siano stati fatti, e questo facilita le cose.
Possiamo risistemare il modulo formattaFoglio togliendo la tabella dei turni di giorno e nominando soltanto i turni fatti di notte (la quantità relativa di diurni e notturni sarà un problema che dovranno scornarsi a livello di reparto).

Bene.
Mi salvo il modulo, però...

Ecco la routine copiaLista rimaneggiata:
Private Sub copiaLista()
    Dim elemento As Range
    Dim k As Integer, z As Integer
    z = 15 '(posizione iniziale della lista dei nomi del mese)
    For k = 1 To Range("ListaNomi").Rows.Count
        ActiveSheet.Cells(z, 6).FormulaR1C1 = Range("ListaNomi").Cells(k, 1).FormulaR1C1
        ActiveSheet.Cells(z, 7).FormulaR1C1 = Range("ListaNomi").Cells(k, 2).FormulaR1C1
        z = z + 1
    Next k
    ActiveWorkbook.Names.Add Name:="ListaNomiMese", RefersTo:="=" & Range(Cells(15, 6), Cells(z - 1, 6)).Address(True, True)
    Griglia Range("ListaNomiMese")

    ActiveWorkbook.Names.Add Name:="ListaReparti", RefersTo:="=" & Range(Cells(15, 7), Cells(z - 1, 7)).Address(True, True)
    Griglia Range("ListaReparti")
    
    ActiveWorkbook.Names.Add Name:="NumeroTurni", RefersTo:="=" & Range(Cells(15, 8), Cells(z - 1, 8)).Address(True, True)
    Griglia Range("NumeroTurni")
    For Each elemento In Range("NumeroTurni")
        elemento.HorizontalAlignment = xlCenter
        elemento.Formula = 0
    Next
    
    
    With ActiveSheet
        With .Cells(Range("ListaNomiMese").Row - 1, 6)
            .FormulaR1C1 = "NOME"
            .HorizontalAlignment = xlCenter
        End With
        Griglia Cells(Range("ListaNomiMese").Row - 1, 6)
        With .Cells(Range("ListaNomiMese").Row - 1, 7)
            .FormulaR1C1 = "REP."
        End With
         Griglia Cells(Range("ListaNomiMese").Row - 1, 7)
        With .Cells(Range("ListaNomiMese").Row - 1, 8)
            .FormulaR1C1 = "GIORNO"
        End With
        Griglia Cells(Range("ListaNomiMese").Row - 1, 8)
        .Columns(6).ColumnWidth = 26
        Exit Sub
        With .Columns(7)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
        
       
        
        With .Columns(8)
            .ColumnWidth = 6
            .HorizontalAlignment = xlCenter
            .Font.Bold = True
        End With
    End With
End Sub
Praticamente ho tolto la colonna dei turni di mattina, rinominando la colonna NumeriNotti in NumeroTurni e basta.

Inoltre ho aumentato un po' l'altezza delle righe.


Adesso cerchiamo di calcolare il numero di turni come avevo fatto precedentemente.

Il principio che avevo usato era:
Conservare in una variabile la stringa eliminata dalla cella;
Considerare la nuova stringa della cella come Bersaglio.Formula;
Cercare nella lista la stringa eliminata dalla cella e ridurre di 1 il numero dei turni;
Cercare nella lista la stringa nuova della cella e aumentare di 1 il numero dei turni.


In fondo è semplicissimo!

Proviamo...
Sub calcola()
    Dim x As Range
    Set x = Range("ListaNomiMese").Find(lastcaption)
    If x Is Nothing = False Then x.Offset(0, 2).Formula = x.Offset(0, 2).Formula - 1
    Set x = Range("ListaNomiMese").Find(Bersaglio.Formula)
    If x Is Nothing = False Then x.Offset(0, 2).Formula = x.Offset(0, 2).Formula + 1
End Sub
Eccola: dovrebbe essere perfetta.

Se vado su una cella vuota, lastcaption è vuota, quindi il range Find di lastcaption è Nothing.
In questo caso, però, non succede nulla perché si agisce sui punteggi solo se il range Find non è Nothing. Quindi si provvererà soltanto a sistemare il punteggio della nuova caption di Bersaglio.
Analogamente, se io vado su una cella con un nome e ci voglio trascrivere il menu vuoto, prima trovo lastcaption, e aggiusto il suo punteggio, quindi il range Find della stringa vuota è Nothing, quindi non succede niente al punteggio.

Dovrebbe funzionare alla perfezione. Molto semplice ed evita i casini di contare ogni volta i nomi, che è di un lento e di un dispendioso pazzesco!

sabato 27 settembre 2014

Ripristino dell'aggiunta intelligente di fogli calendario e creazione di menu personalizzati.

Adesso cosa mi rimane da fare?
La signora della direzione sanitaria invia lo scheletro dei turni, con l'attribuzione dei reparti, mentre l'attribuzione dei medici la fa il primario di ogni singolo reparto.
Una volta che ogni reparto ha stabilito i medici per ogni turno, la signora li trascrive e calcola i turni totali per l'attribuzione anche dei turni straordinari.
Predisponiamo i menu, dunque.

Ma prima devo ripristinare quel meccanismo del caricamento di nuovi fogli solo se sono del mese successivo, senza ripetizioni.
Vediamo la routine nel programma vecchio...

Sub main()
    Dim d As Date, nomeFoglio As String
    
    d = DateAdd("m", 1, Date)      'attribuisce alla variabile locale d il valore del mese successivo alla data attuale
    nomeFoglio = MonthName(Month(d)) & " " & Year(d)    'attribuisce alla stringa nomeFoglio mese e anno della variabile locale d
    
    Sheets.Add          'aggiunge un nuovo foglio
    On Error GoTo x     'gestisce l'errore di tentata rinominazione del foglio
    ActiveSheet.Name = nomeFoglio   'rinomina il foglio con la stringa nomeFoglio.
    ActiveSheet.Move after:=Sheets(Sheets.Count) 'sposta alla fine della cartella il nuovo foglio

        
    formattaFoglio Month(d), Year(d)  'formatta il foglio secondo lo schema
    attribuzioneTurni
    Exit Sub
x:     'gestione dell'errore di tentata rinominazione del foglio: elimina il foglio senza suscitare avvisi di eliminazione.
    Application.DisplayAlerts = False
    ActiveSheet.Delete
    Application.DisplayAlerts = True
End Sub
Mi sembra ben congegnata.
Ho fatto l'aggiunta della chiamata alla routine attribuzioneTurni(), e funziona egregiamente!
Bene! In questo modo, in un paio di secondi, la signora si troverà i turni predisposti, da mandare ai reparti perché essi li compilino con i nomi dei singoli medici.

Ora mi devo occupare dei menu.
Per prima cosa, devo definire di quali reparti i medici fanno parte.
E' bene che mi crei un elenco di medici con l'appartenenza ai relativi reparti.

Mario Sistoletti CAR
Giovanni Mitrali CAR
Arnaldo Coronari CAR
Luigi Di Astoli         CAR
Fernando Ventricoli CAR
Lucio Interni         MED
Marco Medici         MED
Luciano Luminari MED
Antonio Scienziatoni MED
Anselmo Nuvoloni MED
Ovviamente, nomi fittizi, stando qui in ambiente pubblico.

Ecco, ho trasferito dal vecchio abbozzo del programma tutto il blocco di codice che gestisce i menu e mi sono trovato anche qui perfettamente rappresentati i menu.
Ma ora devo intervenire per visualizzare solo i cardiologi o solo gli internisti a seconda del reparto che ha il turno.

Devo intervenire su questa routine del modulo gestoreMenu.
Private Sub caricaMenu(menu As CommandBar, rigaIniziale As Integer, col As Integer)
    Dim button As CommandBarButton
    For k = 1 To Range("ListaNomiMese").Rows.Count
        Set button = menu.Controls.Add(Type:=msoControlButton)
        button.Caption = Range("ListaNomiMese").Cells(k, 1).FormulaR1C1
        button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
    Next k
    Set button = menu.Controls.Add(Type:=msoControlButton)
    button.Caption = ""
    button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
End Sub
In particolare su quella riga di codice evidenziata.

Mi chiedo se sia possibile selezionare le celle in relazione a un criterio...

Ecco: sembra che sia riuscito a produrre una routine efficiente:
Private Sub caricaMenu(menu As CommandBar, rigaIniziale As Integer, col As Integer)
    Dim SiglaReparto As String
    Dim button As CommandBarButton
    If Intersect(Bersaglio, Range("TurnoNotte")) Is Nothing = False Then
            SiglaReparto = Right(Range("Reparto").Cells(Bersaglio.Row - Range("Reparto").Row + 1, 1).Formula, 3)
        Else
            SiglaReparto = Left(Range("Reparto").Cells(Bersaglio.Row - Range("Reparto").Row + 1, 1).Formula, 3)
        End If
    For k = 1 To Range("ListaNomiMese").Rows.Count
        If Range("ListaNomiMese").Cells(k, 1).Offset(0, 1).Formula = SiglaReparto Then
            Set button = menu.Controls.Add(Type:=msoControlButton)
            button.Caption = Range("ListaNomiMese").Cells(k, 1).FormulaR1C1
            button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
        End If
    Next k
    Set button = menu.Controls.Add(Type:=msoControlButton)
    button.Caption = ""
    button.OnAction = "'scrivi """ & button.Caption & """, """ & button.Tag & """'"
End Sub
Con questa, se il turno è attribuito alla cardiologia appare solo un menu di cardiologi, mentre se è attribuito alla medicina appare un menu solo di internisti.

Aggiunta dei turni festivi, con la mia funzione che individua se una data è festiva o no.

Ho ripreso la funzione festivo(valore as String) As Boolean che, data una stringa, ne prende il valore numerico e testa se esprime una data.
Function festivo(valore As String) As Boolean
    If Weekday(CDate(Val(valore) & " " & ActiveSheet.Cells(4, 1).Formula)) = 1 Then festivo = True
End Function
Con questo, individuo nel Calendario quali sono i giorni festivi (per il momento le domeniche, ma la funzione va ampliata a seconda dei giorni festivi diversi dalle domeniche), e vi inserisco anche il reparto che fa il turno festivo.
Mi sembra adeguato inserire il reparto diverso da quello che fa la notte.
La funzione completa ha questo codice:
Dim reparti(1) As String
Dim n As Integer

reparti(0) = "CAR"
reparti(1) = "MED"
 
n = Int(Rnd() * 2)
Range("Reparto").Cells(1, 1).Formula = reparti(n)

For k = 2 To Range("Reparto").Rows.Count
    If n = 0 Then
        n = 1
    Else
        n = 0
    End If
    Range("Reparto").Cells(k, 1).Formula = reparti(n)
    If festivo(Range("Reparto").Cells(k, 1).Offset(0, -2).Formula) Then
        If Range("Reparto").Cells(k, 1).Formula = "MED" Then
            Range("Reparto").Cells(k, 1).Formula = "CAR - MED"
        Else
            Range("Reparto").Cells(k, 1).Formula = "MED - CAR"
        End If
    End If
Next k
E questo è il risultato:

venerdì 26 settembre 2014

Algoritmi di rimescolamento: Knuth.

Vediamo questo "algoritmo di Knuth"...

Ecco: sono riuscito a costruire una routine che, credo, operi secondo questo algoritmo:
Sub funzione()
    Dim carte(3) As Integer
    carte(0) = 123
    carte(1) = 234
    carte(2) = 345
    carte(3) = 456
    Dim i As Integer, j As Integer
    Dim tampone As Integer
    i = 3
    Do While i <> 0
        j = Int(Rnd() * i)
        tampone = carte(j)
        carte(j) = carte(i)
        carte(i) = tampone
        i = i - 1
    Loop
        
    For k = 0 To 3
        Debug.Print carte(k)
    Next k
    Debug.Print "----------------------"
End Sub
Ed ecco una serie di "rimescolamenti"
 345 
 456 
 234 
 123 
----------------------
 234 
 456 
 123 
 345 
----------------------
 456 
 123 
 234 
 345 
----------------------
 456 
 123 
 234 
 345 
----------------------
 234 
 456 
 123 
 345 
----------------------
 234 
 456 
 123 
 345 
----------------------
 456 
 345 
 123 
 234 
----------------------
 456 
 345 
 123 
 234 
----------------------
 456 
 123 
 234 
 345 
----------------------
 456 
 123 
 234 
 345 
----------------------
 456 
 123 
 234 
 345 
----------------------
 456 
 345 
 123 
 234 
----------------------
 234 
 456 
 123 
 345 
----------------------
 234 
 345 
 456 
 123 
----------------------
 234 
 345 
 456 
 123 
----------------------
 456 
 345 
 123 
 234 
----------------------
 234 
 456 
 123 
 345 
----------------------
 234 
 345 
 456 
 123 
----------------------
Sembra che siano abbastanza "casuali"!

Numeri casuali e algoritmo per alternare i due membri di un array con inizio casuale.

Devo attribuire i turni non festivi con un meccanismo casuale, alla medicina o alla cardiologia.
Inizio con uno, quindi alterno i due reparti.

Quindi prima formatto il foglio, quindi sul range Reparto metto il nome del reparto con questo meccanismo casuale.
Ma vediamo come fare una sequenza casuale...

Forse mi conviene fare un array con i nomi dei due reparti.
 Sub main()
    Dim reparti(1) As String
    reparti(0) = "MED"
    reparti(1) = "CAR"
 End Sub
...semplicissimamente elementare e immediato...

Adesso rivediamo un po' come si fanno i numeri casuali...

Sì: ecco un codice che mi permette di vedere la quantità di 1 e di 0 che sono stati estratti a sorte con 50 estrazioni:
 Sub main()
 Dim n As Integer
 Dim nzero As Integer, nuno As Integer
 
 For k = 1 To 50
    n = Int(Rnd() * 2)
    If n = 0 Then nzero = nzero + 1
    If n = 1 Then nuno = nuno + 1
 Next k
 
 Debug.Print nzero
 Debug.Print nuno
 Debug.Print "-----"
 End Sub
Ed ecco i risultati, che mi pare ci stiano piuttosto bene con la "casualità" della cosa:
 26 
 24 
-----
 24 
 26 
-----
 29 
 21 
-----
 24 
 26 
-----
 25 
 25 
-----
 22 
 28 
-----
 27 
 23 
-----
 25 
 25 
-----
 30 
 20 
-----



Fatto questo, estraiamo a sorte dagli elementi del piccolo array:
 Sub main()
 Dim reparti(1) As String
 Dim n As Integer

 
 reparti(0) = "CAR"
 reparti(1) = "MED"
 
 
 For k = 1 To 50
    n = Int(Rnd() * 2)
    Debug.Print reparti(n)
 Next k

 End Sub
...che però mi dà lunghe sequenze sia dell'uno che dell'altro...
CAR
CAR
CAR
MED
MED
CAR
CAR
CAR
CAR
MED
CAR
CAR
MED
CAR
MED
CAR
CAR
MED
CAR
CAR
MED
CAR
MED
CAR
CAR
MED
MED
MED
CAR
CAR
MED
MED
MED
MED
CAR
MED
MED
CAR
MED
MED
MED
CAR
CAR
CAR
CAR
CAR
CAR
MED
CAR
CAR



Meglio ancora, invece, è rendere casuale solo la prima estrazione e successivamente andare per alternanza:
Sub main()
Dim reparti(1) As String
Dim n As Integer

 
reparti(0) = "CAR"
reparti(1) = "MED"
 
n = Int(Rnd() * 2)
Debug.Print reparti(n)

For k = 1 To 50
    If n = 0 Then
        n = 1
    Else
        n = 0
    End If
    Debug.Print reparti(n)
Next k
debug.print "-----"
End Sub
Ed ecco alcune estrazioni ripetute:
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
-----
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
-----
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
-----
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
MED
CAR
-----
Sì: inizia casualmente, quindi si alternano regolarmente.