Blog b = new Blog("marcos blog"); RSS 2.0
# Wednesday, April 28, 2010

Overview

Currently, I’m investigating what can be improved in the new release of Proxy Switcher.

For those of you, who don’t know what Proxy Switcher is a few facts:

  • Proxy Switcher automatically set the proxy depending on your current network connection
  • Beside of that, it can set other things too. E.g.: Execute scripts, change default printer, etc.
  • Click here to go to the project site for more info

Windows 7 Sensor and Location Platform

With Windows 7 Microsoft created a new “Sensor and Location Platform”. Until now, there is not much hardware compatible with it, but this hopefully will change in the near future.
With this API, applications can easily access the available sensors. Here you can find some examples and also a “manual” location provider by clicking on a map.

For the location API, there is a cool free “software” device called Geosense. From their website:

Geosense is designed to use a hybrid mix of geolocation service providers and geolocation methods to pinpoint the most accurate location information possible - including but not limited to WiFi triangulation, cell tower triangulation and IP lookup. Source: http://www.geosenseforwindows.com/

While your laptop vendor has not yet integrated a Windows 7 compatible hardware GPS sensor, Geosense is a good compromise.

Proxy Switcher Integration

For Proxy Switcher this will become a new option for switching proxy configurations, or doing some other stuff as you can see here:

image  (click to open in a new window)

 

Code

To access the Windows 7 location API all you need is the .NET Framework 4. There is a new System.Device.dll (contained in the Client Profile of the .NET Framework). Adding a reference allows you to access the current location.

First, add the following using statement:

using System.Device.Location;

Use the following code  to get the address:

GeoCoordinateWatcher watcher = new GeoCoordinateWatcher();
watcher.TryStart(false, TimeSpan.FromMilliseconds(1000));
// Get the location, including lat, long, speed, altitude, etc.
GeoCoordinate location = watcher.Position.Location;
// Try to get the current address, including street, city, etc.
CivicAddressResolver resolver = new CivicAddressResolver();
CivicAddress address = resolver.ResolveAddress(location);

Summary

With this new functionality, Proxy Switcher is not a pure “Proxy Switcher” anymore, but a multi-functional network/location based “generic” extensible execution tool. New actions can be easily created by developers through the MEF AddIn Framework. More on this in a later post.

 

If you want to become a beta tester, feel free to contact me at “mail” at thisdomain.

Wednesday, April 28, 2010 11:53:18 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
.NET | C# Code | Microsoft | ProxySwitcher | Software | Windows 7
# Monday, June 29, 2009

Proxy Switcher arbeitet derzeit mit dem Internet Explorer und Firefox zusammen. In der Regel greifen Anwendungen automatisch auf die Proxy Einstellungen des Internet Explorers zurück, weshalb es normalerweise vollkommen ausreichend ist.

Hat man nun aber einen Sonderfall, also eine Applikation die nicht auf die Internet Explorer settings zurückgreifen kann, benötigt man ein zusätzlichen “Switcher”.

In diesem Artikel zeige ich, wie einfach es ist, selbst einen zu entwickeln und im Proxy Switcher zu verwenden.

Implementierung

Zunächst erstellt man ein neues Class Library Projekt im Visual Studio 2008. Dies ist auch mit der kostenlosen Variante Visual Studio 2008 Express (C# oder VB.NET) ohne Probleme möglich.

image

Als nächstes fügt man eine Referenz auf ProxySwitcher.Shared.dll hinzu. Dort befindet sich die benötigte Basisklasse.

image

Nun noch die Class1.cs Datei umbenennen, z.B. in DemoSwitcher.cs. Dadurch wird auch automatisch die Klasse umbenannt. Diese Klasse muss nun von der SwitcherBase Klasse erben, wofür außerdem das using statement hinzugefügt werden muss:

image

Über das SmartTag was erscheint wenn man SwitcherBase ausgeschrieben hat, lassen sich automatisch alle benötigten Methoden erzeugen. Das wären “DisableProxy”, “EnableProxy” und die Eigenschaft “Name”.

image

Über die Name Eigenschaft kann man einen Namen vergeben, der später dann im Proxy Switcher angezeigt wird.

Enable und Disable sollte sich von selbst erklären. Innerhalb der Methoden kann man auf this.CurrentProxyEntry zugreifen um die Daten abzurufen, die nun aktiviert/deaktiviert werden sollen.

image

Damit hätten wir die minimal Implementierung beendet.

Erweiterte Funktionen

Man kann nun noch für seinen Switcher Einstellungen laden und speichern und auch einen Settings Dialog als Windows Form anbieten (wofür dann noch eine Referenz auf “System.Windows.Forms” hinzugefügt werden muss):

image 

Deployment

Nachdem man alles soweit fertig hat, muss das Projekt noch kompiliert werden. Dadurch entsteht eine DLL, in meinem Fall PSDemoSwitcher.dll.

Diese muss nur noch in das Verzeichnis kopiert werden, wo auch ProxySwitcher.exe liegt. Also normalerweise “C:\Programme\ProxySwitcher”.

Startet man nun den ProxySwitcher, erscheint im ersten Tab direkt mein neuer Switcher und kann selektiert werden, damit er auch aufgerufen wird.

image

 

Update:

Die dokumentierte Beispielimplementierung gibt es hier zum download.

Monday, June 29, 2009 8:17:43 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [6]
.NET | C# Code | ProxySwitcher | Software | Visual Studio 2008

With the Windows API Code Pack, Microsoft released a really cool managed wrapper for most of the new Windows 7 features. See here for more details and download.

One thing you can do is to create a .NET app with Jumplist support. Jumplists are these cool menus appearing on right click on an icon in the task bar:

image

But there is something wrong with the jumplist: It can only launch new apps! The demo solution for the Code Pack launches notepad and some other things, but there is no word about how to open the same app but with a new form.

So here is my solution, which is not very nice, but it works like a charm.

Scencario:
AppInstance1 is running –> click on a jumplist item starts a new instance –> appInstance2 now knows, that this is the second instance, so appInstance2 calls appInstance1 through remoting (IPC) and terminates–> appInstance1 receives the remoting call und execute something.

Ok, how to implement this?

First of all, create a new Windows Forms app and go to the Program.cs file. Within this class create a new class which works as your service for the remoting part and also a static reference to your form:

static class Program
    {
        public class RemotingService : MarshalByRefObject
        {
            public RemotingService() { }

            public void ShowText(string text)
            {
                Program.formMain.SetTextToTextBox(text);
            }
        }

        internal static Form1 formMain;

Then you will need some remoting code for the communication. I created two methods “InitRemoting” and “CallRunningInstance”. InitRemoting is called from the main method if this is the first instance. CallRunningInstance is called if the main method detects, that this is the second instance.

For the detection I use a system wide mutex.

I hope you get the idea. Because there are some more lines of code I created a demo project for you to download:

 Download the entire working demo solution here.

 

Monday, June 29, 2009 7:42:20 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
.NET | C# Code | Microsoft | Software | Visual Studio 2008 | Windows 7
# Wednesday, January 14, 2009

Das Tool AutoHistory von dem ich vor einigen Tagen bereits geschrieben habe, steht nun auch inkl. source code auf CodePlex zum Download bereit.

Fragen und Anregungen daher bitte gerne weiterhin per Mail oder im Projekt Forum.

-> CodePlex Projekt Seite

Wednesday, January 14, 2009 7:35:32 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
.NET | AutoHistory | C# Code | Office 2007 | Software
# Thursday, September 04, 2008

If you ever got this message and don’t know where it came from, here is the solution.

I got it if I click on “Edit Page” on a publishing page. So after some investigation it seems to be a problem with the web parts on the page.

My page layout contains some EditModePanels, so the user have different views in display and edit mode of the page. Therefore we also have one web part in a web part zone which is in one of the EditModePanels for display only.

<PublishingWebControls:EditModePanel PageDisplayMode="Display">
  <WebPartZone ID="zone1" ...>
      the web part added through the onet.xml site definition

The same web part zone “zone1” does not exists for editing, because I do not want the user to edit the web part. There is another web part zone “zone2” which lives outside of any EditModePanel.

Now comes SharePoint.

If you check out the page and then click on “Edit Page”, everything is working. Except of, that the web part from zone 1 has moved to zone 2! Automatically! Thank you SharePoint…

It seems that SharePoint think it has to show the web part in edit mode. And because the original web part zone is not visible, SharePoint moves the web part to the next available zone which is visible also in edit mode.

So remember:

Never put a web part zone in an EditModePanel which is only visible in display mode.

Maybe the solution is to make the web part not editable, but I didn’t tested this. My fast workaround hack is to create a div instead of the EditModePanel and makes it “runat=server” and give it an ID. Then I wrote this code on top of the page:

<script runat="server">
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    this.navleftdiv.Visible = (this.SPWebPartManager1.DisplayMode == WebPartManager.BrowseDisplayMode);
}
</script>

Now it works like a charm.

Thursday, September 04, 2008 12:52:52 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
.NET | ASP.NET 2.0 | C# Code | SharePoint
# Tuesday, June 10, 2008

Wie bereits hier geschrieben habe ich nun eine Lösung für meinen GPS Logger BT747.

Da das Tool BT747 mit Vista x64 nicht klar kommt, verwende ich also nun gpsbabel um die Daten abzurufen. Es kommt ein bin file raus, was gpsbabel leider nicht in csv konvertieren kann.

Dazu habe ich nun ein eigenes Tool geschrieben, oder besser gesagt von Java in .NET übersetzt.
Hier gibts den download des Tools. Verwendet wird es so:

bt747logconverter.exe data.bin output.csv

Zuvor noch diese Zeile ausführen um das bin file vom Logger zu laden (com4 durch entsprechenden Port ersetzen):

gpsbabel.exe -p "" -w -i mtk -f com4:

Endlich eine funktionierende Lösung unter 64bit…

Tuesday, June 10, 2008 4:35:59 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [1]
.NET | C# Code | Das wahre Leben | Fun | Software
# Thursday, September 27, 2007

Setzt man das SharePoint GridView (Microsoft.SharePoint.WebControls.SPGridView) in eigenen Seiten ein, und benutzt man für die Datenquelle die DataSource Eigenschaft

spGridView.DataSource = dataView;

dann zeigt das GridView beim sortieren keine Pfeile neben der sortierten Spalte an.

Dank Reflector stellt man fest, dass das SPGridView leider die Spalten Sortierung nur anzeigt wenn man als Datenquelle "DataSourceID" verwendet... Warum? Ich sehe da keinen Grund. Deshalb hier in etwa die Implementierung wie es das SPGridView auch intern macht:

      public static void SetGridViewSortArrow(SPGridView spGridView, string sortExpression, SortDirection sortDirection)
      {
         // Show arrow on sorted column
         for (int colIndex = 0; colIndex < spGridView.Columns.Count; colIndex++)
         {
            DataControlField field = spGridView.Columns[colIndex];
            if (((field == null) || string.IsNullOrEmpty(field.SortExpression)) 
|| (field.SortExpression.ToLower(CultureInfo.CurrentCulture) != sortExpression.ToLower(CultureInfo.CurrentCulture))) continue; DataControlFieldHeaderCell cell2 = (DataControlFieldHeaderCell)spGridView.HeaderRow.Cells[colIndex]; Image image = new Image(); if (sortDirection == SortDirection.Ascending) image.ImageUrl = "/_layouts/images/sortup.gif"; else image.ImageUrl = "/_layouts/images/sortdown.gif"; image.Style[System.Web.UI.HtmlTextWriterStyle.MarginLeft] = "2px"; cell2.Controls.Add(image); break; } }

Aufgerufen wird das ganze dann im gridView_Sorting Event nach dem sortieren und vor allem nach dem

spGridView.DataBind();
Thursday, September 27, 2007 1:25:29 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [1]
.NET | ASP.NET 2.0 | C# Code | Microsoft | SharePoint
# Tuesday, September 11, 2007

Wer Anwendungen nutzt, die aus irgendeinem Grund nicht auf die Internet Explorer Proxy Settings zurückgreifen können, der kann Proxy Switcher auch für beliebige weitere Anwendungen erweitern.

Dazu muss zunächst ein Class Library Projekt mit Visual Studio erstellt werden und eine Referenz auf ProxySwitcher.Shared.dll hinzugefügt werden. Das ganze funktioniert natürlich auch mit den kostenlosen Varianten Visual Studio 2005 Express Editions.

Nun einfach eine neue Klasse erstellen, die das Interface ISwitcher im Namespace ProxySwitcher.Shared implementiert. Die Methoden sind eigentlich selbst erklärend und müssen dementsprechend befüllt werden.

image

Die fertig kompilierte DLL dann nur noch in das ProxySwitcher "plugins" Verzeichnis kopieren und ProxySwitcher neu starten. Das neue Plugin steht nun im "Applications" Tab zur Auswahl zur Verfügung.

Demnächst gibts dann hier das Plugin (inkl Sourcecode) für meinen Lieblingsbrowser Opera.

Das Problem an Opera und an vielen anderen Produkten (Firefox, RSS Bandit, etc.) ist, dass man die Anwendungen neu starten muss, damit die neuen Proxy Einstellungen wirksam werden. Der Internet Explorer, bzw. Windows, bietet dafür eine API, mit der man Windows mitteilen kann, dass die Einstellungen aktualisiert wurden.

Tuesday, September 11, 2007 9:35:13 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
.NET | C# Code | ProxySwitcher | Software
# Thursday, August 30, 2007

Ein Kunde, der InfoPath 2003 einsetzt, hat einige Anforderungen, die sich nicht so einfach umsetzen lassen. Da das große weite Internet leider fast nichts dazu hergibt, hier meine Probleme inkl. Lösungen.

Anforderung 1: Eine "multi-select list box", also eine ListBox zum anhaken. In InfoPath 2007 bereits enthalten, muss diese bei 2003 leider selbst gebaut werden. Dazu gab es einen recht guten Artikel bei InfoPathDev, den ich nun (nach deren umgestaltung) leider nicht mehr wieder finde... Deshalb hier nochmal in Kürze:

Die DataSource Struktur (der InternetPortals Knoten ist repeatable und "internetPortalSelected" ist vom Typ boolean):

image

Nun erstellt man eine optionale Section auf dem Formular, und bindet sie an den "PortalsGroup" Knoten. Innerhalb der optionalen Section erstellt man eine Scrolling Region. Dort zieht man dann den "InternetPortals" Knoten hinein und wählt "Repeating Table". In den Eigenschaften hakt man "Allow users to insert and delete rows" und "Include Header" ab. Außerdem sollte die Tabelle natürlich keine Rahmen haben.

Jetzt nur noch das ganze entsprechend ansortieren, die Textbox durch ExpressionBox ersetzen und als default Wert für den Namen etwas schreiben wie "loading...". Die ID muss dabei dem Benutzer gar nicht gezeigt werden und kann gelöscht werden. So sieht das ganze dann aus:

image

Anforderung 2: Und hier wird es komplizierter. Die ListBox soll nun mit Daten von einem WebService befüllt werden, und zwar erst beim öffnen der Section.
Problem 1 daran ist, dass es im IP 2003 Objektmodell keinerlei Events gibt, die ein aufklappen einer section anzeigen. Das einzige Event, was an der Stelle hilft ist das OnContextChange Event. Dieses wird allerdings ziemlich häufig gefeuert, z.b. bei jedem klick irgendwo auf das Formular, etc.

Problem 2 ist, dass man an die selbstgebaute ListBox keine DataSource anbinden kann (zumindest nicht über Wizards). Hier die Lösung (siehe Kommentare im Code): 

[InfoPathEventHandler(EventType = InfoPathEventType.OnContextChange)]
public void OnContextChange(DocContextChangeEvent e)
{
    if (e.Type == "ContextNode")
    {
        LoadPortals();
    }
}
 
private void LoadPortals()
{
    // Zunächst den Knoten finden der den Repeatable Knoten enthält
    IXMLDOMNode ipGroup = thisXDocument.DOM.selectSingleNode("my:Fields/my:PortalsGroup/my:Portals1");
 
    // Ist dieser null dann ist die section nicht aufgeklappt, es muss nichts getan werden
    if (ipGroup == null)
        return;
 
    // Die DataSource abrufen (hier: "GetInternetPortals"). Diese muss zunächst über
    // Tools->Data Connections angelegt werden und das häkchen bei "Automatically retrieve data
    // when form is opened" muss nicht selektiert werden, denn die Daten sollen ja nur bei Bedarf
    // geladen werden, um so das öffnen des Formulars nicht unnötig zu verlangsamen.
    DataObject dob = thisXDocument.DataObjects["GetInternetPortals"];
    dob.Query();
 
    // Hier müssen die Namespaces gesetzt werden um auf das DOM des WebServices zugreifen zu können.
    // Der erste Namespace ist IP 2003 standard, der zweite muss durch den Namespace
    // des WebServices ersetzt werden. 
    IXMLDOMDocument2 d = (IXMLDOMDocument2)dob.DOM;
    d.setProperty("SelectionNamespaces",
          "xmlns:dfs=\http://schemas.microsoft.com/office/infopath/2003/dataFormSolution\
           xmlns:tns=\"http://MY-WEB-SERVICE-URL.COM/SERVICES\"");
 
 
    // Den Knoten des WebService Response holen (wie dieser Knoten heißt lässt sich am einfachsten
    // raus finden, in dem man im debug modus einfach mal den Inhalt von d.xml anschaut).            
    IXMLDOMNode rootNode = d.selectSingleNode("dfs:myFields/dfs:dataFields/
                   tns:GetInternetPortalsResponse/tns:GetInternetPortalsResult");
 
    // Erst jetzt kann man feststellen, ob die Daten bereits in der Listbox enthalten sind
    if (ipGroup.childNodes.length > rootNode.childNodes.length)
        return;
 
    bool removeFirst = true;
 
    foreach (IXMLDOMNode node in rootNode.childNodes)
    {
        // Hier die beiden Knoten die vom WebService zurückgeliefert werden.
        string id = node.selectSingleNode("tns:Id").text;
        string name = node.selectSingleNode("tns:Name").text;
 
        IXMLDOMNode group = thisXDocument.DOM.
                 selectSingleNode("my:Fields/my:PortalsGroup/my:Portals1");
        IXMLDOMNode field = thisXDocument.DOM.
                 selectSingleNode("my:Fields/my:PortalsGroup/my:Portals1/my:InternetPortals");
        IXMLDOMNode newNode = field.cloneNode(true);
        if (removeFirst)
        {
            group.removeChild(field);
            removeFirst = false;
        }
        // Werte vom neuen Knoten setzen
        newNode.selectSingleNode("my:internetPortalId").text = id;
        newNode.selectSingleNode("my:internetPortal").text = name;
        group.appendChild(newNode);
    }
}

 

Demnächst mehr aus der InfoPath Hell...

Thursday, August 30, 2007 8:31:27 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
.NET | Avanade | C# Code | Microsoft | Software
# Tuesday, May 22, 2007

Für mich endete die STC ja bereits gestern, aber als Fazit kann ich sagen, dass zum einen die Location zwar ganz nett war, aber irgendwie hat es mir 2005 in Kassel besser gefallen. Auch die Party am Abend war sehr verteilt in den ganzen Räumen, was eher nicht so toll war, auch wenn es sehr coole Aktivitäten wie z.B. Hochofenaufstieg bei Nacht, GPS Rally, XBox Contest, etc. gab.

Meistens war ich allerdings beim AntMe Coding Contest zu finden, um einigen Teams noch ein paar Tipps zu geben. Und so sah das ganze auf der Leinwand aus, wenn zwei Ameisenvölker gegeneinander angetreten sind:

Moderiert und gemanaged wurde das ganze von Wolfgang und Tom:

  Schönes Bild wie ich finde ;-)

Ok, und damit alle die da waren nochmal nachlesen können, und alle die nicht da waren, aber gerne da gewesen wären, sehen können, was sie verpasst haben, gibt es hier die Slides und Demos der beiden Sessions "AntMe! - Teil 2" und "VSTO - Office Programmierung mit .NET":

Download AntMe! Teil 2 Slides + Demos / Plugins
Download VSTO Slides + Demos

Wer noch fragen hat, gerne hier melden oder per Mail, siehe Slidedecks.

Tuesday, May 22, 2007 11:02:48 AM (W. Europe Standard Time, UTC+01:00)  #    Comments [2]
.NET | AntMe | Avanade | C# Code | Das wahre Leben | Fun | Microsoft | Office 2007 | Student Technology Conference
# Thursday, April 19, 2007

Länger gab es nichts Neues, deshalb hier ein kurzes Update.

1. Ein MOSS 2007 Problem:

Ich versuche über folgende Zeilen (aus einer Windows Forms Anwendung) eine Verbindung zum SharePoint aufzubauen:

SPSite site = new SPSite("http://moss2007/sites/test");
SPWeb web = site.OpenWeb();
SPListCollection lists = web.Lists;

Bereits in der ersten Zeile bekomme ich eine Exception (ausgeführt mit einem MOSS Admin Account) geworfen die auch noch relativ unverständlich ist: "FileNotFoundException: The web application at http://moss2007/sites/test could not be found. (...)"

Mit einem globalen Domain Admin Account (der kein eingetragener MOSS Admin ist, aber auf allen System in der Domäne admin permissions hat) läuft das ganze hingegen ohne Probleme. Ich habe schon rausgefunden, dass der Account in der WSS_Admin_WPG Gruppe sein muss und zusätzlich Zugriff auf die SharePoint_Config Datenbank haben muss, dann gehts... aber wieso? Was genau macht dieser API Call, wenn er nicht über das SharePoint User Objekt Modell geht? Falls jemand eine Erklärung oder ein paar Links zur Hand hat, immer her damit.

2. Die STC 2007 ist zwar noch etwas hin, aber meine Präsentation zum Thema AntMe werde ich bereits auf unserem Avanade Team Meeting vorstellen, dementsprechend bin ich dabei diese bereits vorzubereiten. Einige Plugins sind dafür in der Entstehung und die Slides füllen sich auch langsam mit Inhalt...
Die Agenda steht inzwischen übrigens auch schon fest.

Thursday, April 19, 2007 9:22:29 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [5]
.NET | AntMe | Avanade | C# Code | Das wahre Leben | Microsoft | SharePoint | Student Technology Conference
# Monday, November 20, 2006

Der folgende Code, legt für "s" entweder den String in "myString" fest, oder wenn myString NULL ist, wird der Text auf "nullText" festgelegt.

string myString = null; string s = myString==null ? "nullText" : myString;

Soweit so klar... braucht man ja schon mal ab und zu. Wie ich nun vor kurzem herausgefunden habe, gibt es eine noch kürzere Form:

string myString = null; string s = myString ?? "nullText";

Monday, November 20, 2006 2:40:39 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
.NET | C# Code
# Tuesday, February 28, 2006
Also habe gerade eine Singleton Klasse in C# erstellt, und wollte nun aber doch mal bei Google gucken, was denn so Standard ist...
Dabei bin ich auf mehrere Seiten gestoßen und bin mir nicht mehr sicher, was "gut" ist und was nicht.

Da hätten wir zunächst die "offizielle" MSDN Seite: MSDN: Implementing Singleton in C#
Dann eine andere, die aber recht seriös wirkt: Implementing the Singleton Pattern in C#

Leider weiß ich nicht was ich jetzt für "gut" halten soll... ich hab mich jetzt für:
private static readonly ServiceManager instance = new ServiceManager();
entschieden, was auf der MSDN Seite als threadsafety steht, und auch auf der anderen Seite als Beispiel 4 angewendet wird. Nur das dort steht, dass man unbedingt einen static Constructor braucht, da der C# Compiler sonst die Klasse mit beforefieldinit markiert... sollte da jemand Licht ins dunkle bringen können, immer her damit!
Tuesday, February 28, 2006 6:00:31 PM (W. Europe Standard Time, UTC+01:00)  #    Comments [2]
.NET | C# Code | Microsoft
Statistics
Total Posts: 192
This Year: 4
This Month: 0
This Week: 0
Comments: 291


Sign In
All Content © 2010, Marco Wiedemeyer
DasBlog theme 'Business' created by Christoph De Baene (delarou)