Desenvolvedores.Net - TechBlog

Category Archives: Gerenciadores de Setup

Trabalhando com dados, Combobox e GridPanel

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 4,00 de 5)
Loading...
19 de junho de 2014

Dados_Combobox_GridPanel

Para um melhor entendimento deste Artigo veja o Índice (Rich Internet Applications com Ext.Net)

Trabalhando com dados, Combobox e GridPanel

À essa altura do tópico, já temos conhecimento sobre os componentes, “DirectMethod“, “DirectEvents“, “Listeners“, entre outras coisas. Então é hora de entendermos como podemos exibir os dados aos nossos usuários. Neste tópico eu vou apresentar à vocês a classe “Store“. Então, vamos lá.

Store

O “Store” é um componente que trata os dados entre o cliente e o servidor, normalmente utilizado com componentes do tipo “Grids” e “Comboboxes“. Um “Store” tem a capacidade de filtrar e ordenar os dados, seja local ou remotamente.

Este por sua vez, tem dois pontos importantes:

  • Um “Model“, onde definimos os tipos de dados, nomes de campos, validações, entre outros;
  • Um “Proxy“, responsável pelas requisições entre cliente e servidor, e manter os dados atualizados no lado cliente;
note-taking O componente “Store” tem vários tipos de subclasses, como “JsonStore“, “ArrayStore“, “XMLStore” entre outros. Neste tópico iremos ver os dois mais utilizados “JsonStore” e “ArrayStore“.Mas não se preocupe, quando Ext.NET gera as classes proxyes, automaticamente já é gerado todo o código Ext Js que irá tratar cada tipo de “Store“.

Models

Um “Model” define os dados que o “Store” irá tratar. Em versões anteriores do Ext.NET, este era conhecido como “Record“. No “Model” podemos definir os dados, as validações, nomes para cada campo e formato.

Proxy

Um “Proxy” é utilizado para manter os dados, é uma abstração para o mecanismo de tratamento dos dados e se divide em dois tipos:

  • Client-Side Proxies;
    • MemoryProxy: É um simples “proxy” que mantem os dados na memória do navegador;
    • LocalStorageProxySessionStorageProxy: Usam a nova definição de armazenamento do HTML5. Não funcionam em navegadores mais antigos, pois estes não aceitam HTML5
    • (Eu ainda não estou muito familiarizado com o HTML5 e as novas definições de proxy para o mesmo. Para evitar informação errada, vou parar por aqui 🙂 )

  • Server-Side Proxies;
    • AjaxProxy: Este é um “proxy” de propósito geral, que aceita objetos do tipo array, cada índice é um nome de coluna definido no “model“;
    • JsonPProxy: Permite requisições AJAX “cross-domain“;
    • PageProxy: É um “proxy” que trabalha na página e permite a ligação com eventos no lado servidor;

Entendido o processo de acesso utilizando “Stores”, vamos botar em prática o que aprendemos.

Não se preocupe com os detalhes do código, vamos nos ater apenas ao uso do “Store” e suas peculiaridades.

Antes de começar

Para os dois exemplos, temos que preparar o nosso ambiente antes de começar, para isso, crie dois arquivos de estilo.

combobox.css

.list-item
{
    font: normal 11px tahoma, arial, helvetica, sans-serif;
    padding: 3px 10px 3px 10px;
    border: 1px solid #fff;
    border-bottom: 1px solid #eeeeee;
    white-space: normal;
    color: #555;
}

.list-item h3
{
    display: block;
    font: inherit;
    font-weight: bold;
    margin: 0px;
    color: #222;
}

gridpanel.css

.x-grid-cell-fullName .x-grid-cell-inner
{
    font-family: tahoma, verdana;
    display: block;
    font-weight: normal;
    font-style: normal;
    color: #385F95;
    white-space: normal;
}

.x-grid-rowbody div
{
    margin: 2px 5px 20px 5px !important;
    width: 99%;
    color: Blue;
}

.x-grid-row-expanded td.x-grid-cell
{
    border-bottom-width: 0px;
}

Estes dois arquivos serão responsáveis pela apresentação dos dados no lado cliente.

Continuando …

Exibindo os dados…

Vamos utilizar uma lista de objetos para exibir e tratar os dados do lado cliente. Nos dois exemplos iremos configurar um “Store” para uma “Combbox” e para  uma “GridPanel“.

Veja os exemplos:

Atente apenas ao uso do “Store”. O Resto do código será explicado posteriormente.

GridPanel

ASPX Code

<head id="Head1" runat="server">
    <title>Tutorial Ext.NET</title>
    <link href="../css/gridpanel.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="../Scripts/GridPanel.js"></script>
</head>
<body>
    <form id="Form1" runat="server">
    <ext:ResourceManager ID="ResourceManager1" runat="server" />
    <ext:Window ID="Window1" runat="server" Collapsible="true" Maximizable="true" Icon="Money"
        Title="Orders List" Width="1300" Height="600" X="50" Y="50" Layout="Fit" Closable="false">
        <Items>
            <ext:GridPanel ID="GridPanel1" runat="server" Title="Employees" Frame="true" Header="false"
                Border="false">
                <Store>
                    <ext:Store ID="Store1" runat="server" OnReadData="Store1_ReadData">
                        <Model>
                            <ext:Model ID="Model1" runat="server">
                                <Fields>
                                    <ext:ModelField Name="CustomerID" />
                                    <ext:ModelField Name="EmployeeID" />
                                    <ext:ModelField Name="OrderDate" />
                                    <ext:ModelField Name="RequiredDate" />
                                    <ext:ModelField Name="ShippedDate" />
                                    <ext:ModelField Name="ShipVia" />
                                    <ext:ModelField Name="Freight" />
                                    <ext:ModelField Name="ShipName" />
                                    <ext:ModelField Name="ShipAddress" />
                                    <ext:ModelField Name="ShipCity" />
                                    <ext:ModelField Name="ShipRegion" />
                                    <ext:ModelField Name="ShipPostalCode" />
                                    <ext:ModelField Name="ShipCountry" />
                                </Fields>
                            </ext:Model>
                        </Model>
                    </ext:Store>
                </Store>
                <ColumnModel ID="ColumnModel1" runat="server">
                    <Columns>
                        <ext:Column ID="Column0" runat="server" Text="CustomerID" Width="150" DataIndex="CustomerID">
                        </ext:Column>
                        <ext:Column ID="Column1" runat="server" DataIndex="EmployeeID" Text="EmployeeID"
                            Width="150" />
                        <ext:DateColumn ID="Column2" runat="server" DataIndex="OrderDate" Text="Order Date"
                            Width="150" Format="dd/MM/yyyy" />
                        <ext:DateColumn ID="DateColumn1" runat="server" DataIndex="RequiredDate" Text="Required Date"
                            Width="110" Format="dd/MM/yyyy" />
                        <ext:DateColumn ID="DateColumn2" runat="server" DataIndex="ShippedDate" Text="Shipped Date"
                            Width="110" Format="dd/MM/yyyy" />
                        <ext:Column ID="Column3" runat="server" DataIndex="ShipVia" Text="Ship Via" Width="150" />
                        <ext:Column ID="Column4" runat="server" DataIndex="Freight" Text="Freight" Width="100" />
                    </Columns>
                </ColumnModel>
                <View>
                    <ext:GridView ID="GridView1" runat="server">
                        <GetRowClass Handler="return 'x-grid-row-expanded';" />
                    </ext:GridView>
                </View>
                <SelectionModel>
                    <ext:RowSelectionModel ID="RowSelectionModel1" runat="server" Mode="Single" />
                </SelectionModel>
                <Features>
                    <ext:RowBody ID="RowBody1" runat="server">
                        <GetAdditionalData Handler="orig.rowBody = '<div><span style=\'color: red\'>Ship Information</span>' + '</br/>' + data.ShipName + '</br/>' + data.ShipAddress + '</br/>' + data.ShipCity + (data.ShipRegion == null ? '' : ' - ' + data.ShipRegion) + '</br/>' + (data.ShipPostalCode == null ? '' : data.ShipPostalCode + '</br/>') + data.ShipCountry + '</div>'; orig.rowBodyColspan = record.fields.getCount();" />
                    </ext:RowBody>
                </Features>
                <BottomBar>
                    <ext:PagingToolbar ID="PagingToolbar1" runat="server">
                        <Items>
                            <ext:Label ID="Label1" runat="server" Text="Page size:" />
                            <ext:ToolbarSpacer ID="ToolbarSpacer1" runat="server" Width="10" />
                            <ext:ComboBox ID="ComboBox1" runat="server" Width="80">
                                <Items>
                                    <ext:ListItem Text="1" />
                                    <ext:ListItem Text="2" />
                                    <ext:ListItem Text="10" />
                                    <ext:ListItem Text="20" />
                                </Items>
                                <SelectedItems>
                                    <ext:ListItem Value="10" />
                                </SelectedItems>
                                <Listeners>
                                    <Select Handler="#{GridPanel1}.store.pageSize = parseInt(this.getValue(), 10); #{GridPanel1}.store.reload();" />
                                </Listeners>
                            </ext:ComboBox>
                        </Items>
                        <Plugins>
                            <ext:ProgressBarPager ID="ProgressBarPager1" runat="server" />
                        </Plugins>
                    </ext:PagingToolbar>
                </BottomBar>
            </ext:GridPanel>
        </Items>
    </ext:Window>
    </form>
</body>
note-taking Atenção às linhas em destaque, nestas linhas, estão as definições do “Store”, do “Model” e do “Proxy”.
As definições do modelo estão na tag “<Model>” e dentro de cada tag “<Model>” temos para cada campo a definição “<ModelField>”, onde definimos o nome da propriedade, ou campo, do objeto que será usado no componente “<ColumnModel>” que é definido logo abaixo.
Dentro do “<ColumnModel>” utilizamos a propriedade “DataIndex”, esta tem que ser o mesmo nome definido em “<Model>”, e será este nome que deverá ser usado em seus código javascript.

 

question Mas onde está a definição do <Proxy> dentro do <Store>?

R: Como não definimos um <Proxy> e estamos utilizando uma requisição de página, método “OnReadData=’Store1_ReadData'”, automaticamente será definido como proxy um “PageProxy”.

Code Behind

public partial class GridPanelArray: System.Web.UI.Page
{
	protected void Page_Load(object sender, EventArgs e)
	{
		if(!X.IsAjaxRequest)
		{
			this.BindData();
		}
	}

	protected void Store1_ReadData(object sender, StoreReadDataEventArgs e)
	{
		BindData();
	}

	private void BindData()
	{
		Store store = this.GridPanel1.GetStore();

		store.DataSource = this.Data;
		store.DataBind();
	}

	List<Order> Data
	{
		get
		{
			return Order.Find<Order>();
		}
	}
}

No código acima, temos o método que será chamado quando o “Store” requisitar os dados para o servidor, “Store1_ReadData“. Este método por sua vez chama o método “BindData()“, dentro do método “BindData()”, é carregado uma lista de pedidos (“Order”) e retornados para a propriedade “DataSource” do “Store“, e logo em seguida fazemos a ligação dos dados chamando o método “Store.DataBind()“.

ComboBox

<ext:ComboBox ID="ComboBox4" runat="server" FieldLabel="Selecione vários colaboradores"
	DisplayField="FirstName" Width="320" LabelWidth="130" QueryMode="Local" TypeAhead="true"
	MultiSelect="true">
	<Store>
		<ext:Store ID="Store4" runat="server" Data="<%# ArrayOfEmployees %>" AutoDataBind="true">
			<Model>
				<ext:Model ID="Model4" runat="server">
					<Fields>
						<ext:ModelField Name="EmployeeID" />
						<ext:ModelField Name="FirstName" />
						<ext:ModelField Name="LastName" />
					</Fields>
				</ext:Model>
			</Model>
			<Reader>
				<ext:ArrayReader />
			</Reader>
		</ext:Store>
	</Store>
	<ListConfig>
		<ItemTpl ID="ItemTpl1" runat="server">
			<Html>
				<div class="list-item">
							<h3>ID: {EmployeeID}</h3>
							{FirstName} {LastName}
					</div>
			</Html>
		</ItemTpl>
	</ListConfig>
</ext:ComboBox>

Code Behind

public object ArrayOfEmployees
{
	get
	{
		object result = (from e in Employee.Find<Employee>()
						 select new object[]
						 {
							 e.EmployeeID,
							 e.FirstName,
							 e.LastName,
						 }).ToArray();

		return result;

	}
}

No exemplo do combobox não mudou muita coisa, definimos um “Model” com o mapeamento dos campos, mas  … Temos uma diferença, definimos que o nosso “Reader” irá utilizar uma “ArrayReader”, logo temos que retornar um “Array” de objetos, isto ficou a cargo da propriedade  “ArrayOfEmployees“. A chamada também foi modificada, não usamos o evento “OnDataRead“, e sim a propriedade “Data” com um diretiva ASPX <%# ArrayOfEmployees %>, em ASPX esta diretiva diz ao interpretador para retornar um objeto “datasource“.

Utilizando um DataSource.

O Ext.NET aceita diversos tipos de “DataSources”:

  • LinqDataSource: Aceita comando do tipo “LINQ”;
  • ObjectDataSource: Aceita classes definidas dentro da nossa aplicação. Eu gosto deste 🙂
  • SqlDataSource: Aceita comando do tipo SQL;
  • XmlDataSource: Aceita arquivos do tipo XML. Este datasource requer que seja criado um “Transform” para que possa compreender os dados;
note-taking Você pode usar diversos “datasources” em uma mesma página, para isso basta definir o ID de cada um, e ao utilizar em um “Store“, define o ID do “datasource” na propriedade “Store.DataSourceID“. Cada Store só pode ter um “datasource” definido.
Para saber mais: http://examples.ext.net/#/search/datasource

No exemplo abaixo iremos ver o “ObjectDataSource” em ação.

ASPX Code

<body>
<form id="Form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" />
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="Find" TypeName="Ext.Net.Tutorial.Data.Order" />
<ext:Window ID="Window1" runat="server" Collapsible="true" Maximizable="true" Icon="Money"
	Title="Orders List" Width="1300" Height="600" X="50" Y="50" Layout="Fit" Closable="false">
	<Items>
		<ext:GridPanel ID="GridPanel1" runat="server" Title="Employees" Frame="true" Header="false"
			Border="false">
			<Store>
				<ext:Store ID="Store1" runat="server" DataSourceID="ObjectDataSource1">
					<Model>
						<ext:Model ID="Model1" runat="server">
							<Fields>
								<ext:ModelField Name="CustomerID" />
								<ext:ModelField Name="EmployeeID" />
								<ext:ModelField Name="OrderDate" />
								<ext:ModelField Name="RequiredDate" />
								<ext:ModelField Name="ShippedDate" />
								<ext:ModelField Name="ShipVia" />
								<ext:ModelField Name="Freight" />
								<ext:ModelField Name="ShipName" />
								<ext:ModelField Name="ShipAddress" />
								<ext:ModelField Name="ShipCity" />
								<ext:ModelField Name="ShipRegion" />
								<ext:ModelField Name="ShipPostalCode" />
								<ext:ModelField Name="ShipCountry" />
							</Fields>
						</ext:Model>
					</Model>
				</ext:Store>
			</Store>
			<ColumnModel ID="ColumnModel1" runat="server">
				<Columns>
					<ext:Column ID="Column0" runat="server" Text="CustomerID" Width="150" DataIndex="CustomerID">
					</ext:Column>
					<ext:Column ID="Column1" runat="server" DataIndex="EmployeeID" Text="EmployeeID"
						Width="150" />
					<ext:DateColumn ID="Column2" runat="server" DataIndex="OrderDate" Text="Order Date"
						Width="150" Format="dd/MM/yyyy" />
					<ext:DateColumn ID="DateColumn1" runat="server" DataIndex="RequiredDate" Text="Required Date"
						Width="110" Format="dd/MM/yyyy" />
					<ext:DateColumn ID="DateColumn2" runat="server" DataIndex="ShippedDate" Text="Shipped Date"
						Width="110" Format="dd/MM/yyyy" />
					<ext:Column ID="Column3" runat="server" DataIndex="ShipVia" Text="Ship Via" Width="150" />
					<ext:Column ID="Column4" runat="server" DataIndex="Freight" Text="Freight" Width="100" />
				</Columns>
			</ColumnModel>
			<View>
				<ext:GridView ID="GridView1" runat="server">
					<GetRowClass Handler="return 'x-grid-row-expanded';" />
				</ext:GridView>
			</View>
			<SelectionModel>
				<ext:RowSelectionModel ID="RowSelectionModel1" runat="server" Mode="Single" />
			</SelectionModel>
			<Features>
				<ext:RowBody ID="RowBody1" runat="server">
					<GetAdditionalData Handler="orig.rowBody = '<div><span style=\'color: red\'>Ship Information</span>' + '</br/>' + data.ShipName + '</br/>' + data.ShipAddress + '</br/>' + data.ShipCity + (data.ShipRegion == null ? '' : ' - ' + data.ShipRegion) + '</br/>' + (data.ShipPostalCode == null ? '' : data.ShipPostalCode + '</br/>') + data.ShipCountry + '</div>'; orig.rowBodyColspan = record.fields.getCount();" />
				</ext:RowBody>
			</Features>
		</ext:GridPanel>
	</Items>
</ext:Window>
</form>
</body>

No código em destaque, acima, definimos o “ObjectDataSource” para o nosso “Store“, vamos explicar algumas propriedades:

  • ID: “ObjectDataSource1”: Define o nome do objeto “DataSource” que deverá ser utilizado no “Store“;
  • TypeName: “Ext.Net.Tutorial.Data.Order”: Define o nome do objeto que será instanciado pelo “DataSource”;
  • SelectMethod: “Find”: Nome do método que será chamado para popular o “DataSource”;

Nas outras linhas em destaque, como sempre, definimos o nosso “Model” e já estamos familiarizados com ele.

Utilizando um Proxy

Até que enfim… Falamos tanto do acesso aos dados, de diversas formas, que já estava esquecendo deste tal “proxy“. Vamos ver como trabalhar com o proxy. Para isso, iremos criar um “ASPX Hanlder“.

question Handler? O que é isso?

R: Os Handlers, são manipuladores responsáveis por tratar as requisições HTTP, não têm interface com usuário e são executados de forma síncrona. Devem implementar a interface “System.Web.IHttpHandler“. Um exemplo de requisição que implementa a interface “System.Web.IHttpHandler” é a “System.Web.UI.Page“, que deve ser herdada para criar os formulários em ASPX. (Web Forms). Esta é quem define toda a interatividade com o usuário. OS “Handlers” são úteis quando precisamos de acessar dados do servidor, sem a intervenção do usuário.

Preparando o ambiente.

Antes de começarmos, iremos criar 3 “ASPX Handlers”:

  1. HandlerBase<T>: Abstração para o tratamento dos dados, pois o código de tratamento é semelhante para todos os outros “handlers” que tratam o acesso aos dados.
  2. EmployeeHandler: É o “handler” responsável por exibir os dados dos colaboradores;
  3. OrderHandler: É o “handler” responsável por exibir os dados dos pedidos;

Abstração HandlerBase

public abstract class HandlerBase<T>: System.Web.IHttpHandler
	where T: IModelBase, new()
{
	public void ProcessRequest(HttpContext context)
	{
		//-------------------------------------------------------------------------
		// Definir o tipo de retorno da resposta da requisição.
		// Iremos retornar um objeto do tipo JSON
		//-------------------------------------------------------------------------
		context.Response.ContentType = "application/json";

		//-------------------------------------------------------------------------
		// Recuperar os parâmetros que foram passados pela requisição
		//-------------------------------------------------------------------------
		StoreRequestParameters storeParams = new StoreRequestParameters(context);

		//-------------------------------------------------------------------------
		// Paginar
		//-------------------------------------------------------------------------
		Paging<T> data = DataPaging(storeParams.Start, storeParams.Limit,
			storeParams.Sort.Count() > 0 ? storeParams.Sort[0].Property : "",
			storeParams.Sort.Count() > 0 ? storeParams.Sort[0].Direction.ToString() : "",
			storeParams.GridFilters);
		context.Response.Write(JSON.Serialize(data));
	}

	public bool IsReusable
	{
		get
		{
			return false;
		}
	}

	public static Paging<T> DataPaging(int start, int limit, string sort, string dir, FilterConditions fc)
	{
		List<T> data = DbContext.Find<T>(new T());

		#region Filtrar
		//-------------------------------------------------------------------------
		// Se foi definido alguma condição, temos que filtrar os registros
		//-------------------------------------------------------------------------
		if(fc != null && fc.Conditions.Count > 0)
		{
			foreach(FilterCondition condition in fc.Conditions)
			{
				Comparison comparison = condition.Comparison;
				string field = condition.Field;
				FilterType type = condition.Type;

				object value;
				switch(condition.Type)
				{
					case FilterType.Boolean:
						value = condition.Value<bool>();
						break;
					case FilterType.Date:
						value = condition.Value<DateTime>();
						break;
					case FilterType.List:
						value = condition.List;
						break;
					case FilterType.Numeric:
						if(data.Count > 0 && data[0].GetType().GetProperty(field).PropertyType == typeof(int))
						{
							value = condition.Value<int>();
						}
						else
						{
							value = condition.Value<double>();
						}

						break;
					case FilterType.String:
						value = condition.Value<string>();
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}

				//-------------------------------------------------------------------------
				// Aqui removemos todos os registros que não satisfazem a condição
				//-------------------------------------------------------------------------
				data.RemoveAll(
					item =>
					{
						object oValue = item.GetType().GetProperty(field).GetValue(item, null);
						IComparable cItem = oValue as IComparable;

						switch(comparison)
						{
							case Comparison.Eq:

								switch(type)
								{
									case FilterType.List:
										return !(value as List<string>).Contains(oValue.ToString());
									case FilterType.String:
										return !oValue.ToString().ToLower().Contains(value.ToString().ToLower());
									default:
										return !cItem.Equals(value);
								}

							case Comparison.Gt:
								return cItem.CompareTo(value) < 1;
							case Comparison.Lt:
								return cItem.CompareTo(value) > -1;
							default:
								throw new ArgumentOutOfRangeException();
						}
					}
				);
			}

		}
		#endregion

		#region Ordenar
		//-------------------------------------------------------------------------
		// Ordena os registros de acordo com o que foi passado pelo usuário
		//-------------------------------------------------------------------------
		if(!string.IsNullOrEmpty(sort))
		{
			data.Sort(delegate(T x, T y)
			{
				object a;
				object b;

				int direction = dir == "DESC" ? -1 : 1;

				a = x.GetType().GetProperty(sort).GetValue(x, null);
				b = y.GetType().GetProperty(sort).GetValue(y, null);

				return CaseInsensitiveComparer.Default.Compare(a, b) * direction;
			});
		}
		#endregion

		#region Paginar
		//-------------------------------------------------------------------------
		// Faz a paginação do registro.
		//-------------------------------------------------------------------------
		if((start + limit) > data.Count)
		{
			limit = data.Count - start;
		}

		List<T> rangeData = (start < 0 || limit < 0) ? data : data.GetRange(start, limit);
		#endregion

		//retornar
		return new Paging<T>(rangeData, data.Count);
	}
}

Exemplos de acesso

Classe concreta EmployeeHandler

/// <summary>
/// Summary description for EmployeeHandler
/// </summary>
public class EmployeeHandler: HandlerBase<Employee>
{
}

Classe concreta OrderHandler

/// <summary>
/// Summary description for GridPanelHandler
/// </summary>
public class OrderHandler: HandlerBase<Order>
{
}

Estamos quase lá… Vamos ver agora dois exemplos, uma “Combobox” e uma “GridPanel” acessando os dados por um “proxy” e utilizando um “handler” para tratar os dados.

ASPX Code

<ext:ComboBox ID="ComboBox5" runat="server" FieldLabel="Selecione vários colaboradores"
	DisplayField="FirstName" Width="320" LabelWidth="130" QueryMode="Local" TypeAhead="true"
	MultiSelect="true">
	<Store>
		<ext:Store ID="Store5" runat="server" AutoDataBind="true">
			<Proxy>
				<ext:AjaxProxy Url="../DataHandler/EmployeeHandler.ashx">
					<ActionMethods Read="GET" />
					<Reader>
						<ext:JsonReader Root="data" TotalProperty="total" />
					</Reader>
				</ext:AjaxProxy>
			</Proxy>
			<Model>
				<ext:Model ID="Model5" runat="server">
					<Fields>
						<ext:ModelField Name="EmployeeID" />
						<ext:ModelField Name="FirstName" />
						<ext:ModelField Name="LastName" />
					</Fields>
				</ext:Model>
			</Model>
		</ext:Store>
	</Store>
	<ListConfig>
		<ItemTpl ID="ItemTpl2" runat="server">
			<Html>
				<div class="list-item">
							<h3>ID: {EmployeeID}</h3>
							{FirstName} {LastName}
					</div>
			</Html>
		</ItemTpl>
	</ListConfig>
</ext:ComboBox>

GridPanel

ASPX Code

<head id="Head1" runat="server">
    <title>Tutorial Ext.NET</title>
    <link href="../css/gridpanel.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="../Scripts/GridPanel.js"></script>
</head>
<body>
    <form id="Form1" runat="server">
    <ext:ResourceManager ID="ResourceManager1" runat="server" />
    <ext:Window ID="Window1" runat="server" Collapsible="true" Maximizable="true" Icon="Money"
        Title="Orders List" Width="1300" Height="600" X="50" Y="50" Layout="Fit" Closable="false">
        <Items>
            <ext:GridPanel ID="GridPanel1" runat="server" Title="Employees" Frame="true" Header="false"
                Border="false">
                <Store>
                    <ext:Store ID="Store1" runat="server" PageSize="10">
                        <Proxy>
                            <ext:AjaxProxy Url="../DataHandler/OrderHandler.ashx">
                                <ActionMethods Read="GET" />
                                <Reader>
                                    <ext:JsonReader Root="data" TotalProperty="total" />
                                </Reader>
                            </ext:AjaxProxy>
                        </Proxy>
                        <Model>
                            <ext:Model ID="Model1" runat="server">
                                <Fields>
                                    <ext:ModelField Name="CustomerID" />
                                    <ext:ModelField Name="EmployeeID" />
                                    <ext:ModelField Name="OrderDate" />
                                    <ext:ModelField Name="RequiredDate" />
                                    <ext:ModelField Name="ShippedDate" />
                                    <ext:ModelField Name="ShipVia" />
                                    <ext:ModelField Name="Freight" />
                                    <ext:ModelField Name="ShipName" />
                                    <ext:ModelField Name="ShipAddress" />
                                    <ext:ModelField Name="ShipCity" />
                                    <ext:ModelField Name="ShipRegion" />
                                    <ext:ModelField Name="ShipPostalCode" />
                                    <ext:ModelField Name="ShipCountry" />
                                </Fields>
                            </ext:Model>
                        </Model>
                        <Sorters>
                            <ext:DataSorter Property="CustomerID" Direction="ASC" />
                        </Sorters>
                    </ext:Store>
                </Store>
                <ColumnModel ID="ColumnModel1" runat="server">
                    <Columns>
                        <ext:Column ID="Column0" runat="server" Text="CustomerID" Width="150" DataIndex="CustomerID">
                        </ext:Column>
                        <ext:Column ID="Column1" runat="server" DataIndex="EmployeeID" Text="EmployeeID"
                            Width="150" />
                        <ext:DateColumn ID="Column2" runat="server" DataIndex="OrderDate" Text="Order Date"
                            Width="150" Format="dd/MM/yyyy" />
                        <ext:DateColumn ID="DateColumn1" runat="server" DataIndex="RequiredDate" Text="Required Date"
                            Width="110" Format="dd/MM/yyyy" />
                        <ext:DateColumn ID="DateColumn2" runat="server" DataIndex="ShippedDate" Text="Shipped Date"
                            Width="110" Format="dd/MM/yyyy" />
                        <ext:Column ID="Column3" runat="server" DataIndex="ShipVia" Text="Ship Via" Width="150" />
                        <ext:Column ID="Column4" runat="server" DataIndex="Freight" Text="Freight" Width="100" />
                    </Columns>
                </ColumnModel>
                <View>
                    <ext:GridView ID="GridView1" runat="server">
                        <GetRowClass Handler="return 'x-grid-row-expanded';" />
                    </ext:GridView>
                </View>
                <SelectionModel>
                    <ext:RowSelectionModel ID="RowSelectionModel1" runat="server" Mode="Single" />
                </SelectionModel>
                <Features>
                    <ext:RowBody ID="RowBody1" runat="server">
                        <GetAdditionalData Handler="orig.rowBody = '<div><span style=\'color: red\'>Ship Information</span>' + '</br/>' + data.ShipName + '</br/>' + data.ShipAddress + '</br/>' + data.ShipCity + (data.ShipRegion == null ? '' : ' - ' + data.ShipRegion) + '</br/>' + (data.ShipPostalCode == null ? '' : data.ShipPostalCode + '</br/>') + data.ShipCountry + '</div>'; orig.rowBodyColspan = record.fields.getCount();" />
                    </ext:RowBody>
                </Features>
                <BottomBar>
                    <ext:PagingToolbar ID="PagingToolbar1" runat="server">
                        <Items>
                            <ext:Label ID="Label1" runat="server" Text="Page size:" />
                            <ext:ToolbarSpacer ID="ToolbarSpacer1" runat="server" Width="10" />
                            <ext:ComboBox ID="ComboBox1" runat="server" Width="80">
                                <Items>
                                    <ext:ListItem Text="1" />
                                    <ext:ListItem Text="2" />
                                    <ext:ListItem Text="10" />
                                    <ext:ListItem Text="20" />
                                    <ext:ListItem Text="40" />
                                    <ext:ListItem Text="100" />
                                </Items>
                                <SelectedItems>
                                    <ext:ListItem Value="10" />
                                </SelectedItems>
                                <Listeners>
                                    <Select Handler="#{GridPanel1}.store.pageSize = parseInt(this.getValue(), 10); #{GridPanel1}.store.reload();" />
                                </Listeners>
                            </ext:ComboBox>
                        </Items>
                        <Plugins>
                            <ext:ProgressBarPager ID="ProgressBarPager1" runat="server" />
                        </Plugins>
                    </ext:PagingToolbar>
                </BottomBar>
                <Features>
                    <ext:GridFilters ID="GridFilters1" runat="server">
                        <Filters>
                            <ext:StringFilter DataIndex="CustomerID" />
                            <ext:NumericFilter DataIndex="EmployeeID" />
                            <ext:DateFilter DataIndex="OrderDate">
                                <DatePickerOptions runat="server" TodayText="Now" />
                            </ext:DateFilter>
                            <ext:DateFilter DataIndex="RequiredDate">
                                <DatePickerOptions runat="server" TodayText="Now" />
                            </ext:DateFilter>
                            <ext:DateFilter DataIndex="ShippedDate">
                                <DatePickerOptions runat="server" TodayText="Now" />
                            </ext:DateFilter>
                            <ext:NumericFilter DataIndex="ShipVia" />
                            <ext:NumericFilter DataIndex="Freight" />
                        </Filters>
                    </ext:GridFilters>
                </Features>
            </ext:GridPanel>
        </Items>
    </ext:Window>
    </form>
</body>
</html>

Vejam nos códigos em destaque a definição do proxy, vamos agora explicar o que é cada propriedade que foi utilizada.

  • <Proxy>“: Usamos esta marcação para definir que neste “Store” será usado um “proxy“.
  • <ext:AjaxProxy Url=”../DataHandler/OrderHandler.ashx”>“: Esta marcação define o tipo de “proxy” que iremos usar. Eu utilizo, na maioria das vezes, o “AjaxProxy“, sempre me atendeu. Na definição desta marcação temos a propriedade  “Url” e como podem ver, ela define o caminho do nosso “handler” em relação à página;
  • <ActionMethods Read=”GET” />“: Aqui definimos a ação da requisição como um “GET“, pois vamos solicitar uma informação;
  • <ext:JsonReader Root=”data” TotalProperty=”total” />“: Nesta marcação, definimos o nosso “Reader“, nosso leitor da informação, como eu defini que meu retorno no “handler” será um “JSON” .
    • Lembram da definição do “handler“? context.Response.ContentType = “application/json;

    • Esta marcação tem duas propriedades importantes:
      • Root“, que define o nome da marcação pai de todas, no objeto “JSON” retornado e;
      • TotalProperty“, que define o nome da propriedade que representa o total de registros retornados pela aplicação. Veja abaixo um retorno para facilitar o entendimento destas duas propriedades
        {
         "data": [{
         "ID": 1,
         "EmployeeID": 1,
         "LastName": "Davolio",
         "FirstName": "Nancy",
         "Title": "Sales Representative",
         "TitleOfCourtesy": "Ms.",
         "BirthDate": "1948-12-08T00:00:00",
         "HireDate": "1992-05-01T00:00:00",
         "Address": "507 - 20th Ave. E.Apt. 2A",
         "City": "Seattle",
         "Region": "WA",
         "PostalCode": "98122",
         "Country": "USA",
         "HomePhone": "(206) 555-9857",
         "Extension": "5467",
         "Photo": "System.Byte[]",
         "Notes": "Education includes a BA in psychology from Colorado State University in 1970. 
                   She also completed \"The Art of the Cold Call.\" 
                   Nancy is a member of Toastmasters International.",
         "ReportsTo": 2,
         "PhotoPath": "http://accweb/emmployees/davolio.bmp",
         "New": false
         }],
         "total": 1
        }
whew Ufa! Este tópico foi demorado, mas valeu a pena. Aprendemos nele como acessar dados utilizando “Store“, ‘Models“… Definimos “DataSources“, “Proxies” entre outras funcionalidades.E ainda de quebra conhecemos dois componentes, “ComboBox” e “GridPanel“.

É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Inno Setup – Identificando framework

6
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (3 votos, média: 5,00 de 5)
Loading...
3 de outubro de 2012

Um amigo me perguntou como fazer para que o Inno Setup identificasse qual framework está instalado na máquina e se não tiver, como fazer o download e instalar o mesmo.

Abaixo eu montei um script que faz este processo de instalação do framework, no caso eu usei o Framework 3.5 SP 1 para identificar se o mesmo está instalado ou não.

Uma lista de como identificar quais fw´s estão instalados pode ser obtida em http://msdn.microsoft.com/en-us/kb/kbarticle.aspx?id=318785 e basta trocar a chave de registro na linha 40 do script pela versão do fw que você necessita.

RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5', 'SP', regresult);

No script iremos fazer o download do pacote standalone do fw3.5sp1, que pode ser obtido em http://download.microsoft.com/download/2/0/e/20e90413-712f-438c-988e-fdaa79a8ac3d/dotnetfx35.exe ou no link http://www.microsoft.com/en-us/download/details.aspx?id=22 , para o pacote do installer.

O Inno setup não tem suporte nativo para download de arquivos, mas isso não quer dizer que não possa ser feito.

Para atribuir esta funcionalidade ao Inno iremos usar um pacote chamado “InnoTools Downloader” que pode ser baixado em http://www.sherlocksoftware.org/file.php?id=40
No tutorial eu utilizei a versão 0.3.5.

Documentação online do InnoTools Downloader http://www.sherlocksoftware.org/innotools/itdhelp/index.html.

Bom, agora vamos ao que interessa.

Crie um novo script no Inno.
Uma linha muito importante que deve ser adicionada ao script é a linha

#include ReadReg(HKEY_LOCAL_MACHINE,'Software\Sherlock Software\InnoTools\Downloader','ScriptPath','')

No meu caso é a primeira linha do script, esta linha adiciona a DLL (itdownload.dll) ao nosso script, é ela que iremos usar para baixar o fw.

Aqui iremos usar um pouco de programação pascal, pois é preciso para fazer as chamadas e a montagem das telas do download.

Atenção aos comentários no script, tem bastante informação útil.

//incialização do setup. É sempre chamada pelo Inno ao iniciar o setup
procedure InitializeWizard();
var
    filename  : string;
    regresult : cardinal;
begin
    // verifica se o framework 3.5 sp1 está instalado.
    // mais detalhes para outros frameworks: http://msdn.microsoft.com/en-us/kb/kbarticle.aspx?id=318785
    RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5', 'SP', regresult);

    //se o resultado for um. Então o SP1 está instalado
    //Este resultado é o valor da chave
    if regresult <> 1 then begin
      // definir o caminho do arquivo
      filename := expandconstant('{tmp}\fx3.5sp1.exe');

      // não está instalado. Exibir a mensagem para o usuário se deseja instalar o fw
      if MsgBox('Para continuar a instalação é necessário fazer o download do Framework 3.5 SP1. Deseja continuar?', mbInformation, mb_YesNo) = idYes then begin
          //iniciar o itd
          itd_init;

          //adiciona um arquivo na fila de downloads. (pode se adicionar quantos forem necessários)
          itd_addfile('http://download.microsoft.com/download/2/0/e/20e90413-712f-438c-988e-fdaa79a8ac3d/dotnetfx35.exe', filename);

          //aqui dizemos ao itd que é para fazer o download após o inno exibir a tela de preparação do setup
          itd_downloadafter(wpReady);
        end else begin
          // o usuário optou por não fazer o download do fw, então avisamos de onde ele pode baixar
          MsgBox('O link para download manual do framework é http://download.microsoft.com/download/2/0/e/20e90413-712f-438c-988e-fdaa79a8ac3d/dotnetfx35.exe', mbInformation, mb_Ok);
      end
    end
end;

//Este método é chamado pelo Inno ao clicar em próximo. Neste momento a interface já está criada
procedure CurStepChanged(CurStep: TSetupStep);
var
    filename  : string;
    ErrorCode: Integer;
begin

filename := expandconstant('{tmp}\fx3.5sp1.exe');

if CurStep = ssInstall then begin
    // este passo só irá acontecer após o download do arquivo.
    // para evitar erros, validamos se o arquivo foi baixado. Se não foi, continua com o setup.
    if fileExists(filename) then begin
        // foi baixado. Executar o instalador do fw.
       if not ShellExec('', filename,'', '', SW_SHOW, ewWaitUntilTerminated, ErrorCode) then begin
          // Xi! Deu erro
         if ErrorCode <> 0 then begin
              MsgBox('Erro ao executar o arquivo ' + filename + chr(13) + SysErrorMessage(ErrorCode), mbError, mb_Ok);
         end;
       end
    end;
end;
end;

Abaixo o código completo do Script.

#include ReadReg(HKEY_LOCAL_MACHINE,'Software\Sherlock Software\InnoTools\Downloader','ScriptPath','')
#define MyAppName "Desenvolvedores"
#define MyAppVersion "1.5"
#define MyAppPublisher "desenvolvedores.net"
#define MyAppURL "http://desenvolvedores.net"
#define MyAppExeName "desenvolvedores.exe"

[Setup]
AppId={{006DC1BE-0C38-46DF-AF73-CC79E3A1E9F4}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DisableDirPage=yes
DefaultGroupName={#MyAppName}
OutputBaseFilename=setup
Compression=lzma
SolidCompression=yes

[Languages]
Name: portugues; MessagesFile: C:\Program Files (x86)\Inno Setup 5\Languages\BrazilianPortuguese.isl

[Registry]

[Files]

[Run]

[Code]
//incialização do setup. É sempre chamada pelo Inno ao iniciar o setup
procedure InitializeWizard();
var
    filename  : string;
    regresult : cardinal;
begin
    // verifica se o framework 3.5 sp1 está instalado.
    // mais detalhes para outros frameworks: http://msdn.microsoft.com/en-us/kb/kbarticle.aspx?id=318785
    RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5', 'SP', regresult);

    //se o resultado for um. Então o SP1 está instalado
    //Este resultado é o valor da chave
    if regresult <> 1 then begin
      // definir o caminho do arquivo
      filename := expandconstant('{tmp}\fx3.5sp1.exe');

      // não está instalado. Exibir a mensagem para o usuário se deseja instalar o fw
      if MsgBox('Para continuar a instalação é necessário fazer o download do Framework 3.5 SP1. Deseja continuar?', mbInformation, mb_YesNo) = idYes then begin
          //iniciar o itd
          itd_init;

          //adiciona um arquivo na fila de downloads. (pode se adicionar quantos forem necessários)
          itd_addfile('http://download.microsoft.com/download/2/0/e/20e90413-712f-438c-988e-fdaa79a8ac3d/dotnetfx35.exe', filename);

          //aqui dizemos ao itd que é para fazer o download após o inno exibir a tela de preparação do setup
          itd_downloadafter(wpReady);
        end else begin
          // o usuário optou por não fazer o download do fw, então avisamos de onde ele pode baixar
          MsgBox('O link para download manual do framework é http://download.microsoft.com/download/2/0/e/20e90413-712f-438c-988e-fdaa79a8ac3d/dotnetfx35.exe', mbInformation, mb_Ok);
      end
    end
end;

//Este método é chamado pelo Inno ao clicar em próximo. Neste momento a interface já está criada
procedure CurStepChanged(CurStep: TSetupStep);
var
    filename  : string;
    ErrorCode: Integer;
begin

filename := expandconstant('{tmp}\fx3.5sp1.exe');

if CurStep = ssInstall then begin
    // este passo só irá acontecer após o download do arquivo.
    // para evitar erros, validamos se o arquivo foi baixado. Se não foi, continua com o setup.
    if fileExists(filename) then begin
        // foi baixado. Executar o instalador do fw.
       if not ShellExec('', filename,'', '', SW_SHOW, ewWaitUntilTerminated, ErrorCode) then begin
          // Xi! Deu erro.
         if ErrorCode <> 0 then begin
              MsgBox('Erro ao executar o arquivo ' + filename + chr(13) + SysErrorMessage(ErrorCode), mbError, mb_Ok);
         end;
       end
    end;
end;
end;

Dúvidas? Perguntem.


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s

Inno Setup – Alterando variáveis de ambiente

1
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
23 de fevereiro de 2011
Recentemente, no fórum, foi postada uma dúvida no fórum em relação a alterar as variáveis de ambiente pelo Inno Setup.Aproveitei a deixa para fazer uma postagem sobre isso.Fórum: http://techblog.desenvolvedores.net/bbpress/topic.php?id=11.

O Inno Setup pode ser baixado em http://www.jrsoftware.org/isdl.php é um ótimo gerador de instalador para suas aplicações e não deixa nada a desejar eu o uso desde sua segunda versão para instalar as minhas aplicações.

Para o artigo vou pegar como exemplo a variável Path, que foi a dúvida postada, mas isto serve para qualquer tipo de variável.

Para as versões NT/2000/XP/2003 e atuais. As variáveis de ambiente ficam na seguinte chave do registro.

Para máquina:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment

Para o usuário:
HKEY_CURRENT_USER\Environment

Para as versões 95/98/Me, é necessário modificar o arquivo AUTOEXEC.BAT. Mas não vamos falar sobre isso aqui.

O caminho na chave do registro para Path é um valor do tipo REG_EXPAND_SZ.
Como a documentação do Inno Setup para o Registro afirma na seção, há uma maneira de acrescentar valores a esses elementos:


Em um valor do tipo expandsz, ou multisz, você pode usar uma constante especial chamada {OldData} no parâmetro ValueData. {OldData} será substituída com os dados anteriores do valor do registro. A constante {OldData} pode ser útil se você precisar acrescentar uma string para um valor já existente, por exemplo, {OldData}, {app}. Se o valor não existe ou o valor existente não é um tipo string, a constante {OldData} não será usada.

Original:

On a string, expandsz, or multisz type value, you may use a special constant called {olddata} in this parameter. {olddata} is replaced with the previous data of the registry value. The {olddata} constant can be useful if you need to append a string to an existing value, for example, {olddata};{app}. If the value does not exist or the existing value isn’t a string type, the {olddata} constant is silently removed.

Então, para acrescentar um caminho a uma seção do Registro podemos usar:

[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};C:\foo";

Onde C:\foo é o nosso caminho
Mas deste modo, isso iria repetir o caminho quando você instalar uma segunda vez, o que deve ser corrigido também. No Inno podemos usar uma função em pascal que retorne se a variável Path realmente precisa ser alterada ou não, para isso iremos usar o parâmetro Check

Veja:

[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};C:\foo"; Check: CheckKey('Path','C:\foo')

Vamos escrever a função CheckKey, esta função lê o valor do caminho original e verifica se o valor informado já está na chave do registro.

[Code]

//@param key: chave do registro a ser validada
//@param value: valor a ser procurado
function CheckKey(key:string; value: string): boolean;
var
OrigPath: string;
begin

if not RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
key, OrigPath)
then begin
//vai retornar true se o valor não existir
Result := True;
exit;
end;
// se existir, temos que procurar por ele na variável inteira
// A função Pos() retorna 0 se não encontrado
Result := Pos(';' + value + ';', ';' + OrigPath + ';') = 0;
end;

Código completo do script para estudo:

#define MyAppName "Desenvolvedores"
#define MyAppVersion "1.5"
#define MyAppPublisher "desenvolvedores.net"
#define MyAppURL "http://desenvolvedores.net"
#define MyAppExeName "desenvolvedores.exe"

[Setup]
AppId={{006DC1BE-0C38-46DF-AF73-CC79E3A1E9F4}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DisableDirPage=yes
DefaultGroupName={#MyAppName}
OutputBaseFilename=setup
Compression=lzma
SolidCompression=yes

[Languages]
Name: "portugues"; MessagesFile: "compiler:Languages\Portugues.isl"

[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};C:\foo"; Check: CheckKey('Path','C:\foo')

[Code]

//@param key: chave do registro a ser validada
//@param value: valor a ser procurado
function CheckKey(key:string; value: string): boolean;
var
  OrigPath: string;
begin

  if not RegQueryStringValue(HKEY_LOCAL_MACHINE,
    'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
    key, OrigPath)
  then begin
  //vai retornar true se o valor não existir
    Result := True;
    exit;
  end;
  // se existir, temos que procurar por ele na variável inteira
  // A função Pos() retorna 0 se não encontrado
  Result := Pos(';' + value + ';', ';' + OrigPath + ';') = 0;
end;

Chegamos ao fim do artigo. Desde modo podemos modificar as variáveis de ambiente que precisamos.
Mas isso é apenas uma pequena parte do que podemos fazer com o Inno Setup e acesso ao registro.

Uma ótima fonte de ajuda também é o FAQ do Inno http://www.jrsoftware.org/isfaq.php

É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

About Marcelo

Nascido em Juruaia/MG em uma fazenda de criação de búfalos, e residindo na região Sul do Brasil. Trabalha com desenvolvimento de aplicações desde os 17 anos. Atualmente é Arquiteto Organizacional na Unimake Software. Para saber mais ... http://desenvolvedores.net/marcelo []'s