He provat diferents formes de posar els SVG dins del HTML. He fet servir dues, la primera fent servir el tag HTML <svg>, i dins del tag posar tot el text SVG que un editor com Inkscape ha generat, fent copiar / enganxar. L'altra es fer servir un tag img i en l'atribut src posar el fitxer svg tal qual surt de l'editor. El resultat visualment és el mateix però, almenys en Firefox, el que fa servir el tag svg permet seleccionar els texts de dins del svg, mentre que el que està com atribut d'un img es comporta com un block, sense poder seleccionar res. Curiós.
#21/02/2014 11:34 Programació HTML/CSS Autor: Alex Canalda
Segueixo ampliant la clsDades amb noves funcionalitats, aquest cop són les funcions que permeten executar sentències SQL que retornen un valor numèric, com per exemple un COUNT o un SUM... Són les funcions ExecScalar, com sempre en tres sabors, només amb la sentència SQL, un que a més a més porta la connexió i el final que també porta la transacció. Com sempre el codi.
/// <summary>/// Executa una sentència SQL que no retorna resultats,/// només la primera columna del primer registre./// Obre i tanca conexió a BBDD (la que retorna GetConnStr)/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <returns>Resultat operació (COUNT)</returns>publicint ExecScalar(string SQL)
{
int Results = 0;
SqlConnection localSqlConn;
bool Local = false;
if (SQLConn != null)
{
localSqlConn = SQLConn;
}
else
{
Local = true;
localSqlConn = newSqlConnection();
localSqlConn.ConnectionString = CadConnBBDD;
localSqlConn.Open();
}
Results = ExecScalar(SQL, localSqlConn);
if (Local) localSqlConn.Close();
return Results;
}
/// <summary>/// Executa una sentència SQL que no retorna resultats,/// només la primera columna del primer registre./// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <returns>Resultat operació (COUNT)</returns>publicint ExecScalar(string SQL, SqlConnection pSqlConn)
{
return ExecScalar(SQL, pSqlConn, SQLTrans);
}
/// <summary>/// Executa una sentència SQL que no retorna resultats,/// només la primera columna del primer registre./// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <param name="SqlTrann">Transacció ja creada sobre la conexió (admet null)</param>/// <returns>Resultat operació (COUNT)</returns>publicint ExecScalar(string SQL, SqlConnection pSqlConn, SqlTransaction pSqlTrann)
{
int Results = 0;
object objResult;
SqlCommand SqlComm = newSqlCommand();
SqlComm.Connection = pSqlConn;
if (pSqlTrann != null) SqlComm.Transaction = pSqlTrann;
SqlComm.CommandText = SQL;
SqlComm.CommandType = CommandType.Text;
objResult = SqlComm.ExecuteScalar();
if (objResult != null && objResult != DBNull.Value)
{
Results = int.Parse(objResult.ToString());
}
return Results;
}
#20/02/2014 17:19 Programació C# Autor: Alex Canalda
La clsDades segueix creixent i cada cop es sembla més a una navalla suïssa de les bases de dades. Els mètodes del primer post d'aquesta sèrie executen un SQL i retornen una taula, els mètodes que poso a continuació són els encarregats d'executar SQLs que no retornen una taula com a resultat. Normalment són els SQL relacionats amb INS/UPD/DEL que retornen el nombre de registres afectats. Aquesta funció està disponibles en tres sabors, depenent del detall que volem, la primera que rep un SQL i la funció ja s'apanya a executar-lo, la segona que rep la connexió sobre la que executar l'SQL i la tercera que també rep la transacció. Com sempre el codi:
/// <summary>/// Executa una sentència SQL que no retorna resultats, /// només els registres afectats. /// Obre i tanca conexió a BBDD (la que retorna GetConnStr)/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <returns>Registres afectats</returns>publicint ExecNonQuery(string SQL)
{
int Results = 0;
SqlConnection localSqlConn;
bool Local = false;
if (SQLConn != null)
{
localSqlConn = SQLConn;
}
else
{
Local = true;
localSqlConn = newSqlConnection();
localSqlConn.ConnectionString = CadConnBBDD;
localSqlConn.Open();
}
Results = ExecNonQuery(SQL, localSqlConn);
if (Local) localSqlConn.Close();
return Results;
}
/// <summary>/// Executa una sentència SQL que no retorna resultats/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <returns>Registres afectats</returns>publicint ExecNonQuery(string SQL, SqlConnection pSqlConn)
{
return ExecNonQuery(SQL, pSqlConn, SQLTrans);
}
/// <summary>/// Executa una sentència SQL que no retorna resultats/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <param name="SqlTrann">Transacció ja creada sobre la conexió (admet null)</param>/// <returns>Registres afectats</returns>publicint ExecNonQuery(string SQL, SqlConnection SqlConn, SqlTransaction SqlTrann)
{
int Results = 0;
SqlCommand SqlComm = newSqlCommand();
SqlComm.Connection = SqlConn;
if (SqlTrann != null) SqlComm.Transaction = SqlTrann;
SqlComm.CommandText = SQL;
SqlComm.CommandType = CommandType.Text;
Results = SqlComm.ExecuteNonQuery();
return Results;
}
#19/02/2014 17:10 Programació C# Autor: Alex Canalda
Al principi de fer el blog em vaig plantejar en quin idioma escriure. Vaig escollir el català per comoditat, ja que és el que parlo i penso. Però després vaig pensar que també en anglès, ja que quina millor forma de demostrar algo que fer-ho. Sempre penso en els currículums inflats que veig de tant en tant i que Einstein era un mindundi al costat, però que després alhora de fer algo fan llufa. Així que alehop! Blog multiidioma.
Això a nivell de software suposa varies coses, primer afegir a tots els posts un camp idioma, a les categories també. I després a la part de manteniment del blog cal afegir el corresponent combo amb l'idioma i que carregui en cascada les categories del idioma sel·leccionat. Dins de la pàgina que mostra el blog hi ha un switch-case on posa a tots els texts del blog el que toca segons l'idioma.
A nivell d'HTML5 l'idioma es pot definir a diferents nivells, jo l'he posat a nivell de pàgina on queda així:
<!DOCTYPE html>
<html lang="ca">
[.....]
Com a última cosa és que tinc que re-escriure els posts amb anglés, en HTML serà lang="en"... Yummi! Això ja dependrà de les ganes i el temps disponible.
#19/02/2014 11:34 Programació HTML/CSS Autor: Alex Canalda
La clsDades és el punt de l'aplicació web que per una banda dóna servei als ASHX mitjançant les classes derivades i per l'altra interactua amb les Stored Procedures (SP) de la BBDD. Però a vegades una consulta, o una operació no s'ajusta a la forma d'operar de les SP i per això es fa una consulta adhoc directament al codi. Però per evitar muntar tota la infraestructura necessària per accedir i executar la consulta he ampliat la clsDades. També hi ha tasques i problemes que es van donant en la interacció amb la BBDD de forma habitual, també he posat els mètodes corresponents a la clsDades. Diguem que ara té una part "oficial" amb el GET, GETTOP, INS, UPD, DEL, QUERY i una d'utilitats que aniré posant.
Un del punts problemàtics alhora de interactuar amb la BBDD és l'idioma, que si l'idioma del webserver, que l'idioma de la BBDD, etc... La solució és fer servir un format de data que sigui independent de l'idioma, per exemple el ISO 8601. Per això he fet una mini funció que genera una data en aquesta format.
Altres funcions són un xic més complicades, per exemple, executar una SQL i retornar una DataTable. Aquesta està disponible en tres sabors: un que només rep la sentència SQL que s'ha d'executar, una altra SQL i connexió i la tercera SQL, connexió i transacció. Com sempre el codi:
/// <summary>/// Executa una SQL que retorna resultats contra una conexió, no modifica l'estat de la conexió ni de la transaccio/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <param name="SqlTrann">Transacció ja creada sobre la conexió (admet null)</param>/// <returns>Retorna un NEW DataTable</returns>publicDataTable Exec(string SQL, SqlConnection pSqlConn, SqlTransaction pSqlTrann)
{
DataTable TBL = newDataTable();
SqlCommand SqlComm = newSqlCommand();
SqlDataReader SqlReader;
SqlComm.Connection = pSqlConn;
if (pSqlTrann != null) SqlComm.Transaction = pSqlTrann;
SqlComm.CommandText = SQL;
SqlComm.CommandType = CommandType.Text;
SqlReader = SqlComm.ExecuteReader();
TBL.Load(SqlReader);
return TBL;
}
/// <summary>/// Executa una SQL que retorna resultats contra una conexió, no modifica l'estat de la conexió/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <returns>Retorna un NEW DataTable</returns>publicDataTable Exec(string SQL, SqlConnection pSqlConn)
{
return Exec(SQL, pSqlConn, SQLTrans);
}
/// <summary>/// Executa una sentència SQL que retorna resultats, /// si està informada la variable Conn la fa servir de conexio (ha d'estar oberta), si no crea una i la tanca/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <returns>Retorna un NEW DataTable</returns>publicDataTable Exec(string SQL)
{
DataTable TBL;
SqlConnection localSqlConn;
bool Local = false;
if (SQLConn != null)
{
localSqlConn = SQLConn;
}
else
{
Local = true;
localSqlConn = newSqlConnection();
localSqlConn.ConnectionString = CadConnBBDD;
localSqlConn.Open();
}
TBL = Exec(SQL, localSqlConn);
if (Local) localSqlConn.Close();
return TBL;
}
Altres métodes que segueixen ampliant la clsDades les tractare en futurs posts.
#17/02/2014 12:31 Programació C# Autor: Alex Canalda
M'he animat i he posat RSS al blog, ja que també m'ho han demanat. Doncs dit i fet. El cas és que tampoc és gaire difícil fer-ho. Hi ha unes classes de .NET SyndicationFeed, que ho fan fàcil. Cal fer un ASHX i posar el "using System.ServiceModel.Syndication;", després un parell de bucles, es recupera de la BBDD amb la clsDades els posts i voilà. Com sempre el codi.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Syndication;
using System.IO;
using System.Xml;
using System.Data;
using Blog.data;
namespace Blog
{
/// <summary>/// Summary description for RSS/// </summary>publicclass RSS : IHttpHandler
{
private clsConfig Conf = new clsConfig();
privatestring ConnStr = "";
//Cal posar al Blog.aspx://<link rel="alternate" type="application/rss+xml" href="RSS.ashx" title="Blog Feed"/>publicvoid ProcessRequest(HttpContext context)
{
Conf.Initialize();
ConnStr = Conf.GetVal("CadConn");
SyndicationFeed feed = CreateRecentPostsFeed();
var output = new StringWriter();
var writer = new XmlTextWriter(output);
new Rss20FeedFormatter(feed).WriteTo(writer);
context.Response.ContentType = "application/rss+xml";
context.Response.Write(output.ToString());
}
private SyndicationFeed CreateRecentPostsFeed()
{
List<SyndicationItem> syndicationItems = GetPostsRecents();
returnnew SyndicationFeed(syndicationItems)
{
Title = new TextSyndicationContent("Blog"),
Description = new TextSyndicationContent("Una mica de tot i a vegades més"),
ImageUrl = new Uri("http://blog.canalda.net/Img/laughing_man_p.jpg")
};
}
privateList<SyndicationItem> GetPostsRecents()
{
List<SyndicationItem> Posts = newList<SyndicationItem>();
clsPosts PostsTA = new clsPosts(ConnStr);
Dictionary<string, string> Params = newDictionary<string, string>();
DateTime FPubs = DateTime.Now;
DataTable PostsTBL;
FPubs = FPubs.Subtract(TimeSpan.FromDays(7));
Params.Add("DataPubMesGran", PostsTA.DateTimeToSQL(FPubs, "0"));
Params.Add("Actiu", "true");
PostsTBL = PostsTA.GET(Params);
foreach (DataRow Post in PostsTBL.Rows)
{
Posts.Add(GeneraSyndicacioPost(Post));
}
return Posts;
}
private SyndicationItem GeneraSyndicacioPost(DataRow DR)
{
string id = DR["IdPost"].ToString();
string title = String.Format("Post: {1}", id, DR["Titol"]);
string content = DR["PostText"].ToString();
DateTimeOffset DataPub = new DateTimeOffset((DateTime) DR["DataPub"]);
Uri url = new Uri(String.Format("http://blog.canalda.net/Post.aspx?IdPost={0}", id));
returnnew SyndicationItem(title, content, url, id, DataPub);
// { Summary = new TextSyndicationContent(content) };
}
publicbool IsReusable
{
get
{
returnfalse;
}
}
}
}
#17/02/2014 10:51 Programació C# Autor: Alex Canalda
Ja he pogut veure que a alts DPI les aplicacions Winforms es mostren malament.
Buscant solucions pels Internetes diuen que amb un fitxer Manifest la cosa s'arregla, jo ho he provat i res de res. Al final m'he cansat de perdre temps amb el Manifest i l'he enviat a pastar.
Una altra solució és modificar les propietats d'execució de l'aplicació, potser és la més sencilla per aplicacions de les que no es disposa el codi font.
Aquesta propietat ha d'estar marcada!
En canvi si es disposa del codi font i no es vol preocupar-se de si l'opció està marcada o no es pot afegir el següent. Totes les aplicacions Winforms tenen un fitxer que es diu Program.cs que és on s'especifica el punt d'entrada al programa, i queda tal que:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace CodeGenerator
{
staticclass Program
{
/// <summary>/// The main entry point for the application./// </summary>
[STAThread]
staticvoid Main()
{
if (Environment.OSVersion.Version.Major >= 6) SetProcessDPIAware();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmStoredGenerator());
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
privatestaticexternbool SetProcessDPIAware();
}
}
Es a dir s'afegeix una crida a SetProcessDPIAware. Curiosament aquest funció està definida a la DLL user32.dll. No cal que ens amoïnem de que l'aplicació que estem fent sigui de 64bits, aquesta DLL és de 64bits, encara que el nom enganyi. Suposo que hi ha tantes aplicacions que la tenen referenciada que no va haver-hi collons de canviar-li el nom a user64.dll i que l'univers es col·lapses.
Amb això s'aconsegueix que les aplicacions es vegin bé, però com s'ha mogut el sistema de coordenades és molt possible que els controls (encara que es vegin nítids) estiguin mal col·locats. Hauré de buscar una solució per això altra.
#13/02/2014 12:17 Programació C# Autor: Alex Canalda
Fins que no he tingut el nou monitor he viscut a 96DPI (i jo sense saber-ho), ara ja entro en un nou món: el de alts DPI. Diguem que 96DPI és el 100%, i que segons augmentem aquest percentatge augmenta la mida del que es veu per pantalla. Seria similar a fer Ctrl i '+' en el navegador.
I que és això dels DPI? El DPI és una unitat de mesura que indica quants píxels hi ha en una polzada. També és indicatiu de la mida que té un píxel, ja que pocs píxels en un espai gran vol dir píxels grossos, i al contrari, molts píxels en un espai petit vol dir píxels petits.
Els DPI afecten a com es mostren les aplicacions, els texts, tot per pantalla. Per exemple, suposem que una lletra ocupa 12 píxels d'alçada, això en un monitor vol dir 8 mili metres, però en un altra on els píxels són més grans potser aquests 12px són 12mm, o en un on són més petits 6mm. Clar, això que l'aplicació canvii de mida no mola, ja que si tenim un monitor amb píxels petits ens deixarem la vista, a més que no l'aprofitarem. Lo ideal és disposar de píxels petits (quan més petits millor), però que el software faci més grans els elements de pantalla (textos, controls, ...). Que el software faci les coses més grans es diu escalat i es mesura en percentatge respecte a 96DPI, que es considera el 100%, un 200% és el doble de gran.
Els píxels, quan més petits millor.
Quan es fa un escalat sorgeixen problemes, intenteu agafar una imatge de 1000px i convertir-la a una de 2000px... No es veu nítida. A les aplicacions els passa el mateix, hi ha aplicacions que no escalen bé ja que parteixen d'unitats de mesura basades en píxels, altres escalen bé perquè parteixen de fonts basades en vectors... Les aplicacions d'escriptori, (diferents de les aplicacions web), normalment les faig en Winforms, que es basen píxels i per tant escalen malament... Ara tinc que buscar una solució a això.
Aplicació re-escalada malament, les fonts es veuen borroses. El títol de la finestra es veu bé.Aplicació ben re-escalada
En una altra aplicació també Winforms, amb una pantalla molt més complicada, amb molts més controls, tots han sortit moguts. He perdut una estona posant-los a lloc i finalment s'han vist bé a alts DPI, però m'he portat el projecte a un ordinador amb baixos DPI i el formulari estava desquadrat. Cal doncs dissenyar dos cops el formulari? Pffffff, haig de pensar algo.
#11/02/2014 18:16 Programació Software Autor: Alex Canalda
En tota aplicació web, normalment el seu conjunt de dades creix amb el temps i és necessari buscar la informació sobre la que volem treballar. Per fer-ho es restringeixen les dades aplicant un criteri. Un criteri pot ser peticions d'un municipi, factures d'un client, en un interval de temps, un número d'expedient...
Per aquesta raó a la part superior del grid que mostra tots els registres acostumo a posar un filtre, que permet introduir els valors del criteri que es vol fer servir per restringir els registres del grid. El HTML corresponent a aquest formulari de filtrat és molt senzill. Aleshores l'única part que revesteix alguna dificultat és recollir els paràmetres i enviar-los al ASHX corresponent. El ASHX invocarà al LoadGrid, clsDades QUERY i finalment a la stored procedure QUERY.
Per recollir els paràmetres faig servir una funció Javascript Filtrar (nom molt original). Aquesta funció un cop té els valors dels criteris els posa a la URL que carrega el grid i invoca al mètode del grid trigger reloadGrid. Això últim fa que el grid actualitzi els seus valors amb els nous criteris aplicats. Com sempre el codi:
function Filtrar() {
var FK_Arxiu = $("#frmFiltre #FK_Arxiu").val();
var FK_LlocDiposit = $("#frmFiltre #FK_LlocDiposit").val();
var SigTop = $("#frmFiltre #SigTop").val();
var Capsa = $("#frmFiltre #Capsa").val();
var Inutilitzada = $("input[name=lInutilitzada]:checked").val();
var FK_CapsaNULL = $("input[name=lFK_CapsaNULL]:checked").val();
var URL = 'sigtop.ashx?action=loadgrid&FK_Arxiu=' + FK_Arxiu + '&Capsa=' + Capsa;
URL += '&FK_LlocDiposit=' + FK_LlocDiposit + '&SigTop=' + SigTop;
URL += '&lInutilitzada=' + Inutilitzada + '&lFK_CapsaNULL=' + FK_CapsaNULL;
jQuery("#grdSigTop").jqGrid('setGridParam', { url: URL, page: 1 }).trigger("reloadGrid");
}
L'únic a destacar d'aquest exemple és com recuperar el valor de checkboxes amb jQuery.
#07/02/2014 13:20 Programació Javascript Autor: Alex Canalda