Folgende Aufgabenstellung: In einem Infragistics WinGrid soll eine DataTable angezeigt werden. Am Ende des Grids soll eine TemplateAddRow, also eine leere Zeile zum Anlegen einer neuen Row angezeigt werden. Das Infragistics WinGrid unterstützt diese Funktion von Haus aus so, dass man in diese leere Zeile klicken kann und sobald man beginnt hier Werte einzutragen, dann wird in der DataSource tatsächlich eine neue Row angelegt und in dieser die Werte abgelegt. Gleichzeitig wird eine neue TemplateAddRow angelegt, mit der man wieder eine neue Zeile anlegen kann. Was aber nun, wenn man die Werte nicht im Grid selbst eingeben möchte, sondern in separaten Eingabefeldern? Dann geht man folgendermaßen vor: 1.) Zuerst erstellt man eine Form mit einer Bindingsource, einem Grid und diversen Eingabefeldern. Das Grid und die Eingabefelder werden an die BindingSource gebunden. 2.) Dann stellt man das Grid so ein, dass die TemplateAddRow angezeigt wird. this .ultraGrid1.DisplayLayout.Override.AllowAddNew = Infragistics.Win.UltraWinGrid.AllowAddNew.TemplateOnBottom; 2.) Damit nach dem neu Anlegen einer Zeile das Databinding auf den Eingabefeldern funktioniert muss nun manuell eine neue Zeile angelegt werden. Das Grid erzeugt diese Row in der DataSource erst wenn Werte eingegeben werden, das ist für unseren Fall zu spät. Deshalb nutzen wir das BeforeRowIndert Event. private void ultraGrid1_BeforeRowInsert(object sender, Infragistics.Win.UltraWinGrid.BeforeRowInsertEventArgs e) { this.myBindingSource.AddNew(); this.myBindingSource.Position++; e.Cancel = true; } 3.) Jetzt funktioniert bereits das erste Einfügen. Allerdings erscheint danach keine neue TemplateAddRow mehr. Hier muss man einen kleinen Trick anwenden: Man setzt die ActiveRow auf die TemplateAddRow. Dadurch wird eigentlich die TemplateAddRow nun in eine "echte Row" umgewandelt (was wir bereits ja zuvor manuell gemacht haben. Danach erstellt das Grid eine neue TemplateAddRow (das ist genau das was wir brauchen). Etwas unschön ist nun, dass die neue TemplateAddRow automatisch selektiert wird und wir damit schon wieder einen neuen Datensatz anlegen. Schöner wäre, wenn der gerade erzeugte Datensatz aktiv bleibt. Dazu merkt man sich einfach die aktive Row und setzt die danach wieder. Das Ganze habe ich auf einen Button gelegt, mit dem der Anwender die Eingabe abschließt. Alternativ kann natürlich auch ein anderes Event dafür verwendet werden. Sinnvoll ist hier sicher auch das BeforeSelectChange-Event des Grids entsprechend einzubinden. private void button1_Click(object sender, EventArgs e) { this.myBindingSource.EndEdit(); UltraGridRow temp = this.ultraGrid1.ActiveRow; this.ultraGrid1.ActiveRow = this.ultraGrid1.Rows.TemplateAddRow; this.ultraGrid1.ActiveRow = temp; }
Bin da gerade auf ein nettes Angebot gestoßen. SitePal bietet animierte Figuren, die man sich selber konfigurieren kann. Diese Figuren können dann Texte, die man vorgibt, sprechen, entweder aufgezeichnete Audio-Files oder auch über Texteingabe. Diese Figuren lassen sich dann in eine Web-Seite integrieren. Ist sicher interessant, wenn man ein entsprechendes Projekt hat. Mal sehen, vielleicht kann ich das zukünftig mal nutzen. SitePal Homepage
Bei der Neuinstallation eines Entwicklungsrechners dauert es immer ewig, bis man sich die ganzen nützlichen Helferlein zusammengesucht hat, die man im Laufe der Zeit zu schätzen gelernt hat. Deshalb habe ich mir gedacht, ich schreibe mir mal eine Liste, die ich dann immer wieder ergänzen kann. Und vielleicht ist für den einen oder anderen da auch noch was interessantes dabei. Also so könnte ein Entwicklungsrechner aussehen:
Betriebsystem / Standardanwendungen:
- Windows Vista Ultimate
- Office 2007
- SQL-Server 2005 Express
- SQL-Server 2005 Management Studio
Entwicklungsumgebung:
Tools
Stand 05.10.2007 To be continued...
Will man mit Visual Studio 2008 einem UserControl ein StyleSheet zuordnen, sieht das normalerweise so aus:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs"
Inherits="Controls.WebUserControl1" %>
<head>
<link href="../App_Themes/style.css" rel="stylesheet" type="text/css" />
</head>
<asp:Button ID="Button1" runat="server" Text="Button" /><asp:TextBox ID="TextBox1"
runat="server"></asp:TextBox>
Damit scheint aber der Designer von Visual Studio 2008 Beta 2 Probleme zu haben. Sowohl im Split-View als auch im Design-View wird nach einem Refresh nichts mehr angezeigt.

Bei mir hat hier ein kleiner Trick geholfen. Man verschiebt einfach das Link-Tag mit dem Stylesheet ganz an's Ende des Codes. Dann kann man mit Hilfe der Refresh-Funktion aus dem Kontext-Menü des Design-Panes die Anzeige aktualisieren und siehe da, Button und Textbox werden wieder angezeigt.

Ist eine ASP.Net Seite scrollbar, gibt es den unschönen Effekt, dass die Seite nach dem Postback wieder ganz oben steht. Ich habe mal eine kleine Beispiel-Seite, die diesen Effekt demonstriert:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm2" %> <%@ Register Assembly="cTextBox" Namespace="artiso_lib.UserControls" TagPrefix="cc1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page </title> </head> <body> <form id="form1" runat="server"> <div> <% 1: for (int i = 0; i < 50; i++) 2: { 3: Response.Write(i + "<br>"); 4: } 5: %> </div> </form> </body> </html>
Dieses Problem kann man ganz einfach umgehen, indem man in der Page-Direktive das Attribut MaintainScrollPositionOnPostback="true" einfügt, also:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm2" MaintainScrollPositionOnPostback="true" %>
Im ASP.Net AJAX Toolkit findet man ein ModalPopup, das sehr gute Dienste leistet, will man Meldungen etwas eleganter ausgeben als mit einem schlichten alert(). Die Handhabung ist relativ einfach. Man nehme ein beliebiges Serverseitiges Control und weise diesem den ModalPopupExtender zu. Dann gibt man noch mindestens die ID des Controls an, das den Inhalt des Popups repräsentieren soll und fertig. Will man das Popup allerdings client-seitig per Java-Script aufrufen, muss man ein wenig in die Trickkiste greifen. Das Problem dabei ist, dass der ModalPopupExtender immer ein TargetControl braucht. Ich habe dafür einfach ein unsichtbares DIV erzeugt, das mit runat="server" auch auf dem Server sichtbar gemacht wurde. Dann kann man mit $Find('<ID des ModalPopupExtenders>').show() das Popup aufrufen.
Neu war an diesem Beispiel für mich auch der Style filter: alpha(opacity = 70) mit dem man eine Transparenz erzeugen kann.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm1" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<style type="text/css">
.modalBackground
{
filter: alpha(opacity = 70);
background-color: #CCCCCC;
}
.ErrorMessage
{
background-color: #800000;
color: #FFFFFF;
}
</style>
</head>
<script language="javascript" type="text/javascript"> 1: 2: function okScript() 3: { 4: alert ("OK-Button wurde gedrückt"); 5: } </script>
<body>
<form id="form1" runat="server">
<div id="PopupPseudoPanel" style="display: none" runat="server">
</div>
<cc1:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</cc1:ToolkitScriptManager>
<cc1:ModalPopupExtender ID="ModalPopupExtender" runat="server" PopupControlID="Panel1"
DynamicServicePath="" Enabled="True" TargetControlID="PopupPseudoPanel" BackgroundCssClass="modalBackground"
OkControlID="OkButton" CancelControlID="CancelButton" OnOkScript="okScript();">
</cc1:ModalPopupExtender>
<br />
<br />
<input id="Button2" type="button" value="button" onclick="$find('ModalPopupExtender').show();" />
<asp:Panel ID="Panel1" runat="server" CssClass="ErrorMessage" style="display:none">
<h1>
Popup</h1>
<p>
Das ist ein Popup!</p>
<center>
<asp:Button ID="OkButton" runat="server" Text="OK"></asp:Button>
<asp:Button ID="CancelButton" runat="server" Text="Cancel"></asp:Button>
</center>
</asp:Panel>
</form>
</body>
</html>
Wie man an meinen letzten Posts sieht, beschäftige ich mich momentan sehr intensiv mit LINQ to SQL. Eine wichtige Lektion habe ich gerade eben gelernt. Folgende Ausgangssituation: Ich habe eine ASP.Net Anwendung. In einem DAL lese ich per LINQ ein Objekt aus der Datenbank und schreibe das dann in eine Session-Variable. Der Anwender bekommt das Objekt an der Oberfläche angezeigt und kann es verändern. Nun möchte ich das Objekt wieder in die Datenbank zurückschreiben. Fast alle Tutorials zu LINQ beschreiben, wie einfach es ist, Daten via LINQ zu schreiben. Aber dort wird in einer einzigen Funktion erst gelesen, dann werden die Daten verändert und dann wieder zurückgeschrieben. Das tritt ja aber in der Praxis eher selten auf. Also habe ich mich gefragt, was LINQ tut, wenn ich den DataContext einfach neu instanziere und dann einfach mein Objekt per Add an die Tabelle hinzufüge. Na ja,wie zu erwarten, hat LINQ gemeldet, dass der entsprechende Primary Key bereits existiert. Also was tun? Den gesamten DataContext in die Session legen? Dass das keine gute Idee ist, habe ich ziemlich schnell rausgefunden. Der DataContext skaliert sehr schlecht, da er ein eingebautes Change-Tracking, Identity Tracking und sonst noch ein paar Nettigkeiten hat, die sich aber in einer Web_Anwendung recht eklig verhalten können. Nach einigem Suchen bin ich dann drauf gekommen, dass es auch noch eine Attach-Methode gibt. Damit konnte ich mein Problem schön lösen. Das Ergebnis für das Schreiben sieht nun so aus: public void SaveProductVersion(cProductVersion _ProductVersion)
{
DataContext dataContextProducts = new DataContext("Data Source=NOTEBOOK_VISTA;Initial Catalog=ValuePlanner_2008;Integrated Security=True");
if (_ProductVersion.ID == 0)
{
dataContextProducts.GetTable<cProductVersion>().Add(_ProductVersion);
}
else
{
dataContextProducts.GetTable<cProductVersion>().Attach(_ProductVersion, true);
}
dataContextProducts.SubmitChanges();
}
Hier wird abgefragt, ob es eine neue Produktversion ist (ID == 0). Wenn ja, dann füge ich die per Add hinzu (die ID wird von LINQ automatisch vergeben). Bei einem bestehenden Projekt verwende ich die Attach-Methode. Ganz einfach, wenn mans weiss. Wichtig ist noch, dass alle Properties des Objektes das Attribut UpdateCheck=none benötigen.
Scott Mitchell hat 75 Tutorials zum Thema Data Access mit ASP.Net 2.0 geschrieben. Hier ist praktisch alles an Technologie beschrieben, was für die Programmierung von datenbankbasierten Webanwendungen mit ASP.Net 2.0 notwendig ist. Einzig LINQ wird momentan noch ausgespart. Eine wirklich tolle Sammlung an guten Informationen.
Data Access Tutorials : The Official Microsoft ASP.NET 2.0 Site
Wenn man mit LINQ to SQL arbeitet, dann steht man sehr schnell vor dem Problem, dass man sehen möchte, wass denn LINQ mit der Datenbank so treibt. Der SQL Profiler kann zwar die abgesetzten SQL-Statements anzeigen, die zurückgegebenen Ergebnisse muss mann dann aber von Hand abfragen. Viel eleganter geht es mit dem LINQ to SQL Debug Visualizer von Scott Guthrie. Damit kann man die Abfrage im Debug-Modus von Visual Studio 2008 einfach anzeigen lassen:   Mit dem Execute-Button kann man die Abfrage einfach ausführen und das Ergebnis anzeigen lassen.  Ein echt cooles Tool. Download und eine Installationsanleitung gibt es hier. LINQ to SQL Debug Visualizer - ScottGu's Blog
Ich habe folgendes Datenmodell mit dem LINQ To SQL Designer aufgebaut:
So, eigentlich ganz einfach. Mit folgendem Code kann ich die Daten einlesen: var formList = from f in listDataContext.cFormListItems
orderby f.FormShort
select f; var countryList = from c in listDataContext.cCountryListItems
orderby c.CountryName
select c;
Damit liest LINQ die Daten für die Forms bzw. die Countries ein, die zugeordneten Tabellen bleiben leer, bis man auf das entsprechende Element zugreift, dann wird das nachgeladen (lazy loading). Dieser Effekt ist super, wenn ich z.B. eine Liste der Kunden habe und den Kunden Bestellungen zugeordnet sind und ich teilweise Kunden mit mehreren tausend Bestellungen habe. Dann will ich die Bestellungen nicht gleich alle mitladen. Aber in meinem Fall war es so, dass in den verknüpften Tabellen nur wenige Datensätze stehen und ich deshalb lieber alles auf einen Rutsch laden wollte. Das geht dann so: listDataContext = new cListDataContext("Data Source=NOTEBOOK_VISTA;Initial Catalog=ValuePlanner_2008;Integrated Security=True");
// Define LoadOptions to load some related tables with their parent tables in one select statement
DataLoadOptions loadOptions = new DataLoadOptions();
loadOptions.LoadWith<cFormListItem>(f => f.FormType);
loadOptions.LoadWith<cCountryListItem>(c => c.Market);
listDataContext.LoadOptions = loadOptions;
Diesen Code muss man vor der ersten Abfrage ausführen. Hier werden zwei LoadWith (jeweils für eine untergeordnete Tabelle) mit angegeben. Dies bewirkt, dass die untergeordneten Tabellen mit der jeweiligen Haupttabelle in einem einzigen SELECT Statement aus der Datenbank geladen werden! Ein paar Sachen muss man allerdings beachten:
- Die LoadOptions können nur vor der ersten Abfrage auf dem DataContext zugewiesen werden.
- Die Loadoptions können nicht mehr verändert werden, wenn sie einmal einem DataContext zugewiesen wurden.
- Für mehrere Tabellen ruft man das LoadWith einfach mehrfach auf.
Ich hätte das zwar schöner gefunden, wenn man das auch mit im Designer einstellen könnte, aber es funktioniert einwandfrei.
Hier noch als Beweis das SQL-Statement, das dann tatsächlich auf der Datenbank ausgeführt wird:
SELECT [t0].[ID], [t0].[FormLong], [t0].[FormShort], [t0].[FormTypeID], [t1].[ID] AS [ID2], [t1].[FormTypeLong], [t1].[FormTypeShort]
FROM [values_Forms] AS [t0]
INNER JOIN [values_FormTypes] AS [t1] ON [t1].[ID] = [t0].[FormTypeID]
ORDER BY [t0].[FormShort]
Die Arbeitsbereiche des Team Foundation Server stellen eine Verknüpfung eines lokalen Verzeichnisses mit einem Pfad in der Quellcode-Verwaltung her. Aus verständlichen Gründen kann ein und das selbe Verzeichnis nicht mehrfach zugeordnet werden. Dies gilt insbesondere für den Fall, dass zwei verschiedene Benutzer auf dem gleichen Rechner das selbe Verzeichnis zuordnen wollen, auch wenn es sich auf den gleichen Pfad in der Quellcode-Verwaltung bezieht. Das ganze macht Sinn. Aber was tun, wenn z.B. der Benutzer A nicht mehr existiert und nun Benutzer B seine Projekte übernehmen soll? Der Rat lautet hier, von Benutzer A alle Arbeitsbereiche löschen, bevor der Benutzer gelöscht wird. Der Rat hilft aber wenig, wenn der Benutzer bereits gelöscht wurde und man dann erst die Probleme feststellt. Oder wenn, wie in meinem Fall, durch einen Umzug auf einen anderen Server der Benutzer neu angelegt wurde und zwar den gleichen Namen hat, aber eine andere SID. Die Anpassung der SIDs in der TFS-Datenbank scheint hier nicht richtig funktioniert zu haben.
Den lokalen Workspace zu löschen ist kein Problem, aber das hilft leider in diesem Fall nicht. Die Arbeitsbereiche sind auch zusätzlich noch auf dem Server gespeichert und dort muss man diesen nun löschen, damit man den mit dem neuen Benutzer neu einrichten kann.
Zunächst der Befehl, mit dem man die Workspaces vom Server anzeigen kann:
tf workspaces /owner:* /server:<TFS-Servername> /format:detailed
Um einen Workspace zu löschen kann nun folgender Befehl verwendet werden:
tf workspace /delete <Workspace-Name>;<Benutzername> /server:<TFS-Servername>
Es gibt auch noch den Befehl /updateUserName, der hat in meinem Fall aber leider nicht funktioniert.
Will man in einem Infragistics WebGrid ein Control in einer Zelle einer TemplatedColumn ansprechen, dann ist das gar nicht so einfach wie zunächst vermutet. Man muss ein paar Umwege einlegen. So funktionierts aber dann: protected void UltraWebGrid1_InitializeRow(object sender, Infragistics.WebUI.UltraWebGrid.RowEventArgs e) { DropDownList ddlUnit = ((DropDownList)((CellItem)((TemplatedColumn)e.Row.Cells.FromKey("Unit").Column).CellItems[e.Row.Index]).Controls[1]); .... } Statt dem Controls[1] könnte man auch noch ein FindControl() nutzen, das wäre noch etwas eleganter.
Scott Gutherie hat in seinem Blog eine interessante Artikel-Serie zu LINQ to SQL Data Access Improvements with LINQ to SQL LINQ to SQL is a built-in OR/M (object relational mapper) in .NET 3.5. It enables you to model relational databases using a .NET object model. You can then query the database using LINQ, as well as update/insert/delete data from it. LINQ to SQL fully supports transactions, views, and stored procedures. It also provides an easy way to integrate business logic and validation rules into your data model. Below are some of the articles I've written that explore how to use it: I'll be adding several more articles to my series above in the weeks ahead. I think you'll find that LINQ to SQL makes it dramatically easier to build much cleaner data models, and write much cleaner data code.
Die Beta 2 des Visual Studio 2008 (Oracs) sowie des Visual Studio 2008 Team Foundation Server Beta 2 sind jetzt verfügbar. Visual Studio 2008 Downloads
Unter C# einen Splash-Screen zu erzeugen ist immer etwas Arbeit. Dirk Hammann hat eine kleine Komponente erstellt, die das erledigt. Die Komponente ist Freeware und auch als Quellcode verfügbar. SplashDialog
Auf knapp 200 Seiten haben Kai Gloth und Norbert Eder viele Tipps und Tricks zusammengafasst und diese als PDF-Datei kostenlos bereitgestellt. .NET Casts - dotnetblogbook
Hat man ein typisiertes DataSet und arbeitet darin mit mehreren TableAdaptern, dann stellt sich die Frage, wie Updates auf diesen TableAdaptern in eine Transaktion zusammengefasst werden können. Das Problem dabei ist, dass jeder TableAdapter seine eigene Connection nutzt. Es gibt grundsätzlich zwei Lösungsansätze:
- Man verwendet den TransactionScope aus dem System.Transactions-Namespace. Diese Vorgehensweise hat allerdings den Nachteil, dass die verschiedenen Connections nur über eine Distributed Transactions verwaltet werden können. Das ist nicht unbedingt das Nonplusultra was die Performance angeht und eigentlich ja auch mit Kanonen auf Spatzen geschossen, da typischerweise alle TableAdapter ja auf die gleiche Datenbank gehen dürften und damit die Ditributed Transactions etwas überkandidelt sind.
- Man verwendet eine Connection für alle TableAdpater. Hierzu gibt es glücklicherweise die Möglichkiet, dass man die Connection der Tableadapter austauschen kann. Man kann z.B. einfach die Connection des ersten TableAdapters allen anderen zuweisen und dann auf dieser Connection mit BeginTransaction eine neue Transaktion beginnen und diese dann mit Commit bzw. RollBack abschließen. Alternativ kann man natürlich auch manuell eine Connection erzeugen und diese dann allen TableAdaptern zuweisen.
Eine sehr schöne Beschreibung dieser Lösungsansätze hat John Waters unter folgendem Link veröffentlicht: ADO.NET : Getting TableAdapters to participate in transactions
Wenn der Team-Explorer behauptet, einen nicht mehr zu kennen und mit folgender Meldung behauptet, man stellt keine bekannte Identität dar,

dann kann dieses Problem behoben werden, indem man den Cache des Team-Explorers löscht. Dieser liegt unter Vista im Ordner C:\Users\Thomas\AppData\Local\Microsoft\Team Foundation\1.0\Cache. Hier einfach alle Unterordner löschen und schon klappts wieder mit dem TFS.
| |