Hallo,
mit "new" geht es natürlich. Aber dann hast du ja das Problem, dass die Eigenschaft nicht verwendet wird, wenn du irgendwas mit allen Controls machen willst (z.B. über die Controls-Collection eines Fensters) - dann wird ja die Standard-Enabled-Eigenschaft verwendet. Daher kam das für mich hier nicht infrage.
@ Björn: Ich habe jetzt noch die Behandlung des VisibleChanged-Events eingebaut.
@ chilic: Ein Ausblenden der Buttons kam in diesem Fall leider nicht infrage, weil der Nutzer sehen soll, dass es in bestimmten Modi noch weitere Funktionen gibt. Auch das Überschreiben bzw. Ändern der Enabled-Eigenschaft war nicht wirklich attraktiv, da diese Eigenschaft leider nicht überschrieben werden kann (zumindest nicht im Framework 2.0).
Nachdem in einem Projekt die Anforderung auftauchte, auch bei deaktivierten Buttons Tooltips anzuzeigen, da man ja trotzdem gern wissen möchte, was der Button machen würde bzw. warum er im Moment deaktiviert ist, habe ich mich im guten alten Internet auf die Suche nach einer Lösung gemacht.
Das Problem ist ja, dass deaktivierte Controls keinerlei Mausereignisse auslösen bzw. behandeln und damit das Anzeigen eines Tooltips in diesem Zustand nicht möglich oder vorgesehen ist. Bei meiner Suche nach einer Lösung habe ich durchgängig nur einen Ansatz gefunden: Im Fenster (bzw. Parent-Control des Buttons) soll das MouseMove-Ereignis behandelt und damit ein Tooltip angezeigt werden, sobald der Cursor über den deaktivierten Button fährt.
Die Lösung erschien mir aber aus zwei Gründen nicht ideal:
1. In dem Projekt, in dem die Anforderung aufgetaucht ist, gibt es etliche Fensterklassen ohne gemeinsame Basisklasse und die Lösung müsste daher in mehrere Klassen eingebunden werden.
2. Mir erscheint der Ansatz auch wenig performant, bei jeder noch so irrelevanten Mausbewegung zu prüfen, ob sich der Cursor gerade über dem deaktivierten Button befindet.
Da es (glücklicherweise) nur eine Basisklasse für alle Buttons in dem Projekt gibt, habe ich im besten Fall also nach einer schnell zu implementierenden Lösung gesucht, die ich nur in dieser einen Klasse umsetzen bzw. einbinden muss.
Ich habe mir also überlegt, beim Deaktivieren eines Buttons ein transparentes Panel über den Knopf zu legen, welches den gleichen Tooltip erhält wie der Button selbst. Beim Aktivieren des Buttons wird das Panel wieder entfernt, sodass der Button auch wieder angeklickt werden kann.
Der Reihe nach:
Wie schon erwähnt, gibt es in dem Projekt bereits eine Basisklasse für alle Buttons, die unter anderem die Möglichkeit bereitstellt, einfach Tooltips anzuzeigen. Die (auf das Wesentliche reduzierte) Klasse sah dafür so aus:
public class MyTooltipButton : Button
{
private Tooltip tt;
/// <summary>
/// Ermittelt den Text, der im Tooltip angezeigt werden soll, oder legt diesen fest
/// </summary>
[DefaultValue("")]
public string Tooltip
{
get { return tt.GetToolTip(this); }
set { tt.SetToolTip(this, value); }
}
}
Um nun den Code der Klasse nicht unnötig mit Tooltip-bezogenen Dingen aufzublähen, habe ich eine Manager-Klasse erstellt, die sich um alle Dinge für das Anzeigen der Tooltips kümmert.
Wie bereits erwähnt, wird in dieser Lösung ein transparentes Panel verwendet, welches über den Knopf gelegt wird. Die Implementierung der Klasse für unsichtbare bzw. transparente Panels sieht dabei wie folgt aus:
In der Manager-Klasse (im Beispiel: MyTooltipManager) brauchen wir nun 3 Objekte, um das Vorhaben umzusetzen: die Instanz des Buttons, eine Instanz des transparenten Panels und natürlich eine ToolTip-Instanz. Die Klasse bekommt also 3 Instanzvariablen, um diese Objekte zu halten, sowie einen Konstruktor, der die Instanz des Buttons entgegennimmt:
public class MyTooltipManager : IDisposable
{
private Control ctrl;
private ToolTip tooltip = new ToolTip();
private TransparentPanel panel;
public MyTooltipManager(Control ctrl)
{
this.ctrl = ctrl;
// Benötigte Events des Controls registrieren
ctrl.EnabledChanged += new EventHandler(ctrl_EnabledChanged);
ctrl.VisibleChanged += new EventHandler(ctrl_VisibleChanged);
ctrl.LocationChanged += new EventHandler(ctrl_LocationChanged);
ctrl.SizeChanged += new EventHandler(ctrl_SizeChanged);
}
}
Um das Ganze später bei Bedarf auch für andere Controls einsetzen zu können, habe ich hier gleich Control als Typ verwendet und nicht Button.
Wie im Code auch zu sehen ist, werden im Konstruktor diverse Ereignisse des Controls registriert. Einmal brauchen wir die Information, wenn das Control deaktiviert bzw. aktiviert wird, um das Panel einzufügen bzw. zu entfernen. Andererseits müssen wir wissen, wenn sich die Größe oder die Position des controls ändert, um das Panel dann ebenfalls zu skalieren oder eben zu verschieben. Außerdem müssen wir noch wissen, wenn das Control sichtbar wird, um dann ggf. im deaktivierten Zustand das Panel hinzuzufügen.
Die EventHandler dafür sehen so aus:
private void ctrl_EnabledChanged(object sender, EventArgs e)
{
if (ctrl.Enabled)
{
RemovePanel();
}
else
{
// Panel nur hinzufügen, wenn das Control auch sichtbar ist
if (ctrl.Visible)
{
AttachPanel();
}
}
}
private void ctrl_VisibleChanged(object sender, EventArgs e)
{
// Panel einfügen, wenn noch nicht geschehen
if (ctrl.Visible && !ctrl.Enabled && (this.panel == null || this.panel.Parent != this.ctrl.Parent))
{
AttachPanel();
}
}
private void ctrl_LocationChanged(object sender, EventArgs e)
{
if (this.panel != null)
{
this.panel.Location = this.ctrl.Location;
}
}
private void ctrl_SizeChanged(object sender, EventArgs e)
{
if (this.panel != null)
{
this.panel.Size = this.ctrl.Size;
}
}
Ich denke, der Code bedarf keiner weiteren Erklärung. Die im EnabledChanged-Handler verwendeten Methoden AttachPanel() und RemovePanel() sehen folgendermaßen aus:
/// <summary>
/// Erstellt das Panel, falls noch nicht vorhanden, und legt es über das Control
/// </summary>
private void AttachPanel()
{
if (this.panel == null)
{
this.panel = new TransparentPanel();
}
this.panel.Location = this.ctrl.Location;
this.panel.Size = this.ctrl.Size;
this.tooltip.SetToolTip(this.panel, this.tooltip.GetToolTip(this.ctrl));
this.panel.Parent = this.ctrl.Parent;
this.panel.BringToFront();
}
/// <summary>
/// Entfernt das Panel
/// </summary>
private void RemovePanel()
{
if (this.panel != null)
{
this.panel.Parent = null;
}
}
In der Methode AttachPanel wird das transparente Panel also erstellt (falls noch nicht vorhanden) und anschließend in Größe und Postion dem Control angepasst. Dann wird noch der Tooltip des Controls übernommen und das Panel in den Vordergrund geschoben, sodass es auch in jedem Fall vor dem Control platziert wird.
Wie im bisher gezeigten Quellcode schon ersichtlich, bekommt die Manager-Klasse noch eine Dispose()-Methode verpasst:
Jetzt benötigen wir nur noch eine Möglichkeit, um den Text des Tooltips festzulegen, und schon haben wir alles, was wir brauchen:
/// <summary>
/// Ermittelt den Text, der im Tooltip angezeigt werden soll, oder legt diesen fest
/// </summary>
public string Tooltip
{
get { return this.tooltip.GetToolTip(this.ctrl); }
set
{
this.tooltip.SetToolTip(this.ctrl, value);
if (this.panel != null)
{
this.tooltip.SetToolTip(this.panel, value);
}
}
}
Damit ist die Manager-Klasse schon komplett. Jetzt muss diese nur noch in die am Anfang gezeigte Button-Klasse eingebunden werden.
Die "neue" Basisklasse für die Buttons des Projekts sieht dann so aus:
public class MyTooltipButton : Button
{
private MyTooltipManager ttManager;
public MyTooltipButton()
{
ttManager = new MyTooltipManager(this);
}
/// <summary>
/// Ermittelt den Text, der im Tooltip angezeigt werden soll, oder legt diesen fest
/// </summary>
[DefaultValue("")]
public string Tooltip
{
get { return ttManager.Tooltip; }
set { ttManager.Tooltip = value; }
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
ttManager.Dispose();
}
base.Dispose(disposing);
}
}
Das war's dann auch schon. Ein fertiges Beispiel-Projekt ist im Anhang zu finden. Über Meinungen und Verbesserungsvorschläge würde ich mich natürlich freuen!
edit: Behandlung des VisibleChanged-Ereignisses hinzugefügt
Hallo,
ich habe eine Word-Vorlage, die Makros enthält. Diese nehme ich, um dann per MailMerge einen Serienbrief zu erstellen (per MailMerge.Execute()). Dann speichere ich das entstandene Zieldokument ab. Leider sind in dem Zieldokument die Makros aus der Vorlage nicht mehr drin.
Gibt es einen Weg, die Makros der Vorlage in das Zieldokument zu übernehmen? Oder kann ich per Interop die Makros aus der Vorlage auslesen und in das Zieldokument einfügen?
Hi,
ich habe die Situation, dass ich ein Serienbriefdokument habe. Mithilfe der Methoden MailMerge.OpenDataSource(...) und MailMerge.Execute() wird die Datenquelle angebunden und die Seriendruckfelder werden aufgefüllt/ersetzt. Klappt auch bis hierher aller wunderbar.
Nun soll das Programm aber erweitert werden, sodass auch formatierter Text als Inhalt für die Seriendruckfelder dienen soll. Dazu gibt es im Programm RichTextBoxen zur Definition der formatierten Texte. Die RTF-Daten werden in der Datenquelle gespeichert.
Naiv wie ich war, dachte ich natürlich "Word kann doch mit RTF gut umgehen, das geht auch bei Seriendruckfeldern". Aber leider Fehlanzeige: Im "fertigen" Word-Dokument wird der RTF-Code als Klartext angezeigt.
Hat jemand eine Idee, wie ich RTF-Text per MailMerge einfügen kann, sodass die Formatierung auch richtig von Word übernommen wird?
Danke, ich hab einige interessante Dinge gelesen, die mir bestimmt auch weiterhelfen. Aber eins vermisse ich (vielleicht geht es ja auch gar nicht?):
Ist es möglich, mit .NET ein richtiges AddIn zu schreiben, was ein Menü oder einen Button in Notes einfügt - so, wie es mit Microsoft Office möglich ist?
Weiß dazu jemand was?
Ich hatte mit Notes noch nicht wirklich was zu tun, hab aber gelesen, dass man dort mit JavaScript oder einer anderen Sprache (ich glaub "Lotus Notes Script" heißt sie) so etwas machen kann. Damit könnte ich ja dann eine von mir erstellte .NET-Anwendung mit Parametern oder so aufrufen, die dann irgendwas macht. Lieber wäre mir aber, wenn ich "nur" eine Anwendung/DLL erstellen und installieren müsste statt eine Anwendung und ein Script in Notes.
Vielleicht ist ja sowas möglich. Wäre schön, wenn sich da jemand näher auskennt.
Hi,
ich muss demnächst - falls möglich - ein AddIn für Lotus Notes erstellen, das eine gerade geöffnete E-Mail an ein externes Programm übergibt oder - besser noch - die E-Mail selbst in eine andere Datenbank (SQL Server) schreibt.
Ich habe schon im Netz gesucht und bin auf die interop.lotus.dll und interop.domino.dll gestoßen. Diese hab ich auch schon in eine Testprojekt eingefügt und ein wenig erkundet. Allerdings weiß ich nicht so wirklich weiter.
Hat denn schon mal jemand mit .NET für Lotus Notes entwickelt? Hat jemand Erfahrung mit AddIns bzw. der AddIn-Entwicklung für Lotus Notes? Geht das überhaupt mit den Interop-Assemblies?
1) Für jedes einzelne Objekt in dieser Gruppe ein einzelnes Move-Command,
diese dann aber in einem Makro-Command kapseln. (schließlich soll mein Rückgängig machen jetzt nicht erst eins nach dem andern zurückhüpfen, sondern wieder alle als Gruppe)
(Und das Makro-Command sorgt natürlich dafür, dass die Unter-Commands alle ausgrührt werden, also sogesehen mehr ein Command-Group-Command)
Hi,
ich würde diese erste Variante bevorzugen, weil du dieses Command-Group-Command dann nicht nur verwenden könntest, um die gleiche Aktion für mehrere Objekte zu gruppieren, sondern evtl. auch, um verschiedene Aktionen für ein Objekt zu kapseln, die in einem Command ausgeführt werden sollen, also eben so wie ein Makro.
Ich hatte gerade ein ähnliches Problem: den umgekehrten Weg von System.Type zu DbType. Der Vollständigkeit halber und falls es jemanden interessiert, hier die Methode die ich dafür erstellt habe:
public static DbType ConvertToDbType(Type type)
{
string name = type.Name;
DbType val = DbType.String; // Standard
try
{
val = (DbType)Enum.Parse(typeof(DbType), name, true);
}
catch
{ } // wenn keine Konvertierung möglich -> Standard zurückgeben
return val;
}
Die Methode (zumindest so ähnlich) hab ich übrigens im MSDN-Forum gefunden. Sie sollte eigentlich bei den meisten Standardtypen funktionieren...
Ja, stimmt natürlich. Es gibt ja Overloading
OK, aber trotzdem müsste ich für alle Typen eine Methode schreiben, was ich ursprünglich eigentlich nicht wollte... Was Allgemeingültiges gibt es nicht? Warum ist Convert.ChangeType() nicht gut (vorausgesetzt, man behandelt die Ausnahmen angemessen)?
Danke für den Tipp und den Link.
Allerdings sehe ich den Nutzen (für mich) nicht. Der TypeConverter kann doch immer noch von einem Typ zu einem anderen konvertieren, oder nicht? Da bräuchte ich ja etliche Converter-Klassen, um meine Umwandlungen zu machen... das möchte ich nur sehr ungern.
Die Methode Convert.ChangeType scheint übrigens genau die zu sein, die ich gesucht habe... ich muss mich aber noch ein wenig mit den FormatProvidern rumschlagen, damit Datumsangaben und Zahlen richtig konvertiert werden können.
Ich lese aus einer Datenbank Standardwerte verschiedenster (Basis-)Typen aus. Und die sind immer eine Zeichenkette - ist halt so gespeichert...
Nach dem Auslesen sollen diese Standardwerte natürlich bei Bedarf zugewiesen werden können. Und dazu muss ich sie konvertieren oder von mir aus auch parsen.
Ich wollte mir eben nur ersparen, eine lange if-else if-else Anweisung (oder auch switch) zu schreiben, um letztendlich doch irgendeinen Typen zu vergessen...
Daher hätte ich gern eine Methode, die "alle" Typen konvertieren kann.
Hallo,
ich möchte einen String in einen anderen Typ konvertieren. Ich habe den Zieltyp und man kann davon ausgehen, dass der String einen "passenden" Wert enthält.
Es kann sein, dass der String "100" ist und der Zieltyp int. Andererseits könnte der String "false" enthalten und der Zieltyp könnte in dem Fall boolean sein.
Gibt es vielleicht im Framework schon eine Methode, die so das kann oder fällt jemandem eine hübsche kleine Methode ein?
Oder gibt es wirklich keine andere Möglichkeit als so etwas:
public static object ConvertToType(string value, Type type)
{
if (type.Equals(typeof(int)))
return Convert.ToInt32(value, formatprovider);
else if (type.Equals(typeof(bool)))
return Convert.ToBoolean(value, formatprovider);
...
}
ja, es ist ganz offensichtlich, dass da einer oder einzelne am Werk sind, die einseitig die Einwohnerzahl vorantreiben. Ich weiß leider nicht so recht, was ich davon halten soll.
Mittlerweile glaube ich, dass da ein Bot geschrieben wurde oder zumindest was ähnliches. So schnell die Einwohnerzahl jetzt steigt (undNUR die Einwohnerzahl), kann es ja schon nicht mehr mit rechten Dingen zugehen...
Ich danke auch Dir, herbivore. Wenigstens hast Du konstistente Ergebnisse erhalten
Danke auch für die weiteren Varianten. Interessant, wie viele Wege (und das waren bestimmt noch nicht alle) es für so eine doch relativ simple Aufgabe gibt
Hier ist plötzlich Replace viel langsamer und Split auf dem zweiten Platz.
Warum diese Schwankungen? Liegt es vielleicht nur an der wahrscheinlich unterschiedlichen Systemauslastung zum Zeitpunkt der Messung? Oder hat es wirklich mit der Anzahl der Durchläufe zu tun?
Jedenfalls war in meinen Tests die Schleifenvariante immer die schnellste. Die Methode mit unsicherem Code hab ich jetzt nicht probiert
Hi,
ich habe Strings, in denen Punkte (".") vorkommen. Ich möchte wissen, wie viele Punkte im String enthalten sind, und das möglichst effizient, da das sehr oft gebraucht wird.
Meine Quick&Dirty-Methode macht Folgendes:
int count = meinString.Split('.').Length - 1
Ich denke mal, dass es wohl effizientere Möglichkeiten gibt, da hier ja erst ein Array erzeugt wird.
Ich bin mir nicht sicher, ob reguläre Ausdrücke effizienter sind oder ob es vielleicht irgendwas superschneller gibt, dass ich nur übersehen habe.
Hi,
ich habe ein GridView, in dem ich mir automatisch die Eigenschaften einer Liste mit Bankverbindungsdaten anzeigen lasse.
Die Klasse hat die Eigenschaften KtoNr, KtoName, KtoOrt (die drei sind vom Typ String) und Bank (ist ne eigene Klasse mit den Eigenschaften Name, Blz usw.).
Gibt es eine einfache Möglichkeit, wie ich mir in der Liste auch den Namen und die Blz der Bank anzeigen lassen kann?
Ich dachte erst, es würde vielleicht so gehen:
Aber so geht es leider nicht. Dann dachte ich an eine ToString()-Methode in Bank, die mir den Namen ausgibt. Dann könnte ich mit DataField="Bank" zumindest den Namen anzeigen. Aber dann habe ich immer noch nicht die Blz...
OK, Danke. Und wie dann weiter?
OtherClass soll sowas wie eine Liste darstellen (nach außen). Das DataSet ist dabei die Datenquelle.
Wenn ich nun also MyClass.MyProperty[5].Test eingebe, dann passiert intern folgendes:
MyClass hat ein Property mit dem sinnvollen Namen MyProperty:
public MyProperty
{
get { return new OtherClass(ref myDataSet); }
}
Hier hab ich das ref schon mal mit eingebaut. Hiermit wird also der Konstruktor von OtherClass aufgerufen.
OtherClass besitzt dann auch einen Indexer, mit dem ich auf die Zeilen des DataSets zugreifen kann. Der Indexer gibt mir dann ein Objekt der Klasse ThirdClass zurück, welche ebenfalls das DataSet als Parameter erhält:
public this[int index]
{
return new ThirdClass(myDataSet, index);
}
ThirdClass wiederum greift dann auf die ihr übergebene Zeile des DataSets zu und gibt durch ihre Properties die Zellen/Spalten der Zeile im DataSet zurück.
Das funktioniert wie schon gesagt auch alles. Dur wenn ich über die ebenfalls in der Klasse OtherClass bereitgestellten Methoden Add(), Remove() etc. das DataSet verändere, ändert es sich nicht in MyClass, wo es eigentlich gehalten werden soll.
Wenn ich dann also wie von Dir vorgeschlagen im Konstruktor ref verwende, gringt mir das also was? Am Ende des Konstruktors ist mit dem DataSet doch noch nichts passiert?!
Seltsamerweise funktioniert der ganze Spaß, wenn ich das DataSet schon in der ersten Klasse befülle und das befüllte DataSet an die 2. Klasse übergebe. In der 2. Klasse (OtherClass im obigen Beispiel) kann ich dann auch neue Zeilen hinzufügen, ändern oder löschen - funktioniert alles wunderbar: Es wird auch in der ersten Klasse entsprechend geändert.
Warum funktioniert es nicht, wenn ich ein leeres DataSet übergebe (also nicht null, sondern new DataSet()) ? Selbst, wenn ich in das DataSet eine Tabelle packe (egal, ob die leer ist oder die Struktur der Datenbanktabelle besitzt), funktioniert es nicht.
Hi,
Objekte - also auch DataSets - werden in C# ja soweit ich weiß per Referenz übergeben (genauer: die Referenz auf das Objekt wird als Wert übergeben).
Nun habe ich folgende Situation:
Ich habe eine Klasse, die eine Reihe von DataSets hält. Diese sind anfangs NULL.
Nun erzeuge ich aus dieser Klasse heraus Objekte anderer Klassen. Dabei übergebe ich eines der DataSets dem Konstruktor als Parameter. Ungefähr so:
private DataSet myDataSet;
public OtherClass MyMethod()
{
return new OtherClass(myDataSet);
}
In der Klasse OtherClass nehme ich also das DataSet entgegen und fülle es mit Daten aus einer Datenbank:
public class OtherClass
{
private DataSet myDataSet;
public OtherClass(DataSet paramDataSet)
{
myDataSet = paramDataSet;
// DataSet 'myDataSet' mit Daten befüllen
FillDataSet();
}
}
Funktioniert ja im Prinzip auch wunderbar. Nur leider ist myDataSet in der ersten Klasse danach immer noch NULL. Da nur die Referenz auf das DataSet übergeben wird, müsste doch myDataSet in beiden Klassen den gleichen Inhalt haben, weil sie beide auf das gleiche DataSet-Objekt zeigen, oder nicht?
Wo ist mein Denkfehler bzw. welche Lösungen gibt es?
Übrigens: Übergabe per ref oder out geht ja nicht, da ich in der Klasse OtherClass noch mehr Logik habe. ref und out würden ja bedeuten, dass ich am Ende des Konstruktors die geänderten DataSets für die Rückgabe fertig haben müsste...