<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>GDI+</title><link>http://activedeveloper.dk/weblogs/mrjs/category/99.aspx</link><description>Kategorien dækker håndtering af grafik via kode. Det gøres i .NET med GDI+ API'et</description><managingEditor>Jesper Jensen</managingEditor><dc:language>da-DK</dc:language><generator>.Text Version 0.95.2004.102</generator><item><dc:creator>Jesper Jensen</dc:creator><title>Farverigt sitemap</title><link>http://activedeveloper.dk/weblogs/mrjs/archive/2007/01/09/6886.aspx</link><pubDate>Tue, 09 Jan 2007 15:43:00 GMT</pubDate><guid>http://activedeveloper.dk/weblogs/mrjs/archive/2007/01/09/6886.aspx</guid><wfw:comment>http://activedeveloper.dk/weblogs/mrjs/comments/6886.aspx</wfw:comment><comments>http://activedeveloper.dk/weblogs/mrjs/archive/2007/01/09/6886.aspx#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://activedeveloper.dk/weblogs/mrjs/comments/commentRss/6886.aspx</wfw:commentRss><trackback:ping>http://activedeveloper.dk/weblogs/mrjs/services/trackbacks/6886.aspx</trackback:ping><description>&lt;p&gt;Jeg fik stille udfordringen at lave et sitemap med thumbnails af de enkelte sider i et website ;-)&lt;/p&gt;
&lt;p&gt;Da jeg er nørd, er den pragmatiske løsning med at grabbe alle sider i websitet med [Ctrl+Alt+Print Screen], ikke aktuel. Nej opgaven skriger på en dynamisk løsning - for hvad nu hvis indholdet skifter - så er der jo ikke andet at gøre end at starte Photoshop op og cutte/resize - hmmm.&lt;/p&gt;
&lt;p&gt;Løsningen kunne være at lave en tabel med en masse IFrames der var scaleret med Zoom css-egenskaben - stadig pragmatisk, og hulens langsom i realiteten.&lt;/p&gt;
&lt;p&gt;Nej - jeg valgte at gå på strandhugst i &lt;em&gt;Windows.Forms &lt;/em&gt;namespacet. I .NET 2.0, er der blandt andet dukket en WebBrowser-kontrol op, og den er lige hvad vi har brug for. Specielt har jeg hæftet mig ved metoden: &lt;em&gt;DrawToBitmap&lt;/em&gt; - den må kunne bruges. Så igang med at lave en testside der indeholder en &lt;em&gt;TextBox, Button&lt;/em&gt; og en &lt;em&gt;Image&lt;/em&gt;-kontrol. Click-eventen på knappen skal så tage den indtastede Url og bruge WebBrowser objektet til at downloade alt indholdet, og så skrive en Jpg-fil som Image-kontrollen så referere til. Simpelt - lidt.&lt;/p&gt;
&lt;p&gt;Det første man lige skal bide mærke i, er at WebBrowser kontrollen ikke befinder sig i eet af standard webapplikations referencerne, men i &lt;em&gt;System.Windows.Forms&lt;/em&gt;, så man skal lige indsætte en reference til dll'en i sit webprojekt først. Min code-behind endte op med at se sådan ud: &lt;/p&gt;
&lt;div style="FONT-SIZE: 10pt; BACKGROUND: white; COLOR: black; FONT-FAMILY: Consolas"&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   18&lt;/span&gt;     &lt;span style="COLOR: #2b91af"&gt;WebBrowser&lt;/span&gt; wb;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   19&lt;/span&gt;     &lt;span style="COLOR: blue"&gt;protected&lt;/span&gt; &lt;span style="COLOR: blue"&gt;void&lt;/span&gt; Button1_Click(&lt;span style="COLOR: blue"&gt;object&lt;/span&gt; sender, &lt;span style="COLOR: #2b91af"&gt;EventArgs&lt;/span&gt; e)&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   20&lt;/span&gt;     {&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   21&lt;/span&gt;         wb = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;WebBrowser&lt;/span&gt;();&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   22&lt;/span&gt;         wb.ScrollBarsEnabled = &lt;span style="COLOR: blue"&gt;false&lt;/span&gt;;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   23&lt;/span&gt;         wb.Size = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; System.Drawing.&lt;span style="COLOR: #2b91af"&gt;Size&lt;/span&gt;(1024, 768);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   24&lt;/span&gt;         wb.DocumentCompleted += &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;WebBrowserDocumentCompletedEventHandler&lt;/span&gt;(wb_DocumentCompleted);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   25&lt;/span&gt;         wb.Navigate(inpUrl.Text);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   26&lt;/span&gt;     }&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   27&lt;/span&gt; &lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   28&lt;/span&gt;     &lt;span style="COLOR: blue"&gt;void&lt;/span&gt; wb_DocumentCompleted(&lt;span style="COLOR: blue"&gt;object&lt;/span&gt; sender, &lt;span style="COLOR: #2b91af"&gt;WebBrowserDocumentCompletedEventArgs&lt;/span&gt; e)&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   29&lt;/span&gt;     {&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   30&lt;/span&gt;         &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt; thumb = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt;(102, 76);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   31&lt;/span&gt;         &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt; bm = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt;(1024, 768);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   32&lt;/span&gt;         wb.DrawToBitmap(bm, &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;Rectangle&lt;/span&gt;(0, 0, 1024, 768));&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   33&lt;/span&gt;         &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; filename = &lt;span style="COLOR: blue"&gt;string&lt;/span&gt;.Format(&lt;span style="COLOR: #a31515"&gt;"~/{0:N}.jpg"&lt;/span&gt;, &lt;span style="COLOR: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid());&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   34&lt;/span&gt; &lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   35&lt;/span&gt;         bm.Save(Server.MapPath(filename));&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   36&lt;/span&gt;         Image1.ImageUrl = filename;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   37&lt;/span&gt;         bm.Dispose();&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   38&lt;/span&gt;     }&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;&lt;!--EndFragment--&gt;Det virker dog ikke til min store overraskelse....!&lt;/p&gt;
&lt;p&gt;Jeg får fejlmeddlelsen:&lt;/p&gt;
&lt;p&gt;&lt;i&gt;ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Hvad går der galt? Jo - WebBrowser kontrollen er jo et com-objekt, der har fået en .NET wrapper, og som et sådan, er vi nød til at instantierer, Navigerer, gemme og destruerer i samme tråd. Som det fremgår af ovenstående kode, så er der hooket op på &lt;em&gt;DocumentComplete&lt;/em&gt;ev-enten, som eksekveres i en anden tråd. Løsningen ligger i at putte al koden ind i en metode der eksekveres i en separat tråd. &lt;/p&gt;
&lt;p&gt;For at vi kan være sikker på at WebBrowser objektet har downloadet al indholdet, er man i stedet for nød til at lave et loop der holder hovedtråden ind til Url'en er helt hentet. Med lidt omskrivning kom jeg frem til følgende funktionelle kode ;-)&lt;/p&gt;
&lt;div style="FONT-SIZE: 10pt; BACKGROUND: white; COLOR: black; FONT-FAMILY: Consolas"&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   19&lt;/span&gt;     &lt;span style="COLOR: blue"&gt;protected&lt;/span&gt; &lt;span style="COLOR: blue"&gt;void&lt;/span&gt; Button1_Click(&lt;span style="COLOR: blue"&gt;object&lt;/span&gt; sender, &lt;span style="COLOR: #2b91af"&gt;EventArgs&lt;/span&gt; e)&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   20&lt;/span&gt;     {&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   21&lt;/span&gt;         &lt;span style="COLOR: blue"&gt;string&lt;/span&gt; filename = &lt;span style="COLOR: blue"&gt;string&lt;/span&gt;.Format(&lt;span style="COLOR: #a31515"&gt;"~/TN/{0:N}.jpg"&lt;/span&gt;, &lt;span style="COLOR: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid());&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   22&lt;/span&gt;         &lt;span style="COLOR: #2b91af"&gt;Thread&lt;/span&gt; thread = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;Thread&lt;/span&gt;(&lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;ParameterizedThreadStart&lt;/span&gt;(GetUrl));&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   23&lt;/span&gt;         thread.SetApartmentState(&lt;span style="COLOR: #2b91af"&gt;ApartmentState&lt;/span&gt;.STA);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   24&lt;/span&gt;         thread.Start(Server.MapPath(filename));&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   25&lt;/span&gt;         Image1.ImageUrl = filename;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   26&lt;/span&gt;     }&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   27&lt;/span&gt; &lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   28&lt;/span&gt;     &lt;span style="COLOR: blue"&gt;private&lt;/span&gt; &lt;span style="COLOR: blue"&gt;void&lt;/span&gt; GetUrl(&lt;span style="COLOR: blue"&gt;object&lt;/span&gt; file)&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   29&lt;/span&gt;     {&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   30&lt;/span&gt;         &lt;span style="COLOR: blue"&gt;try&lt;/span&gt;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   31&lt;/span&gt;         {&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   32&lt;/span&gt;             &lt;span style="COLOR: #2b91af"&gt;WebBrowser&lt;/span&gt; wb = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;WebBrowser&lt;/span&gt;();&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   33&lt;/span&gt;             wb.ScrollBarsEnabled = &lt;span style="COLOR: blue"&gt;false&lt;/span&gt;;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   34&lt;/span&gt;             wb.Size = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; System.Drawing.&lt;span style="COLOR: #2b91af"&gt;Size&lt;/span&gt;(1024, 768);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   35&lt;/span&gt;             wb.Navigate(inpUrl.Text);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   36&lt;/span&gt;             &lt;span style="COLOR: green"&gt;//Vent til dokument er hentet&lt;/span&gt;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   37&lt;/span&gt;             &lt;span style="COLOR: blue"&gt;while&lt;/span&gt; (wb.ReadyState != &lt;span style="COLOR: #2b91af"&gt;WebBrowserReadyState&lt;/span&gt;.Complete)&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   38&lt;/span&gt;             {&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   39&lt;/span&gt;                 System.Windows.Forms.&lt;span style="COLOR: #2b91af"&gt;Application&lt;/span&gt;.DoEvents();&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   40&lt;/span&gt;             }&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   41&lt;/span&gt; &lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   42&lt;/span&gt;             &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt; bm = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt;(1024, 768);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   43&lt;/span&gt;             wb.DrawToBitmap(bm, &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;Rectangle&lt;/span&gt;(0, 0, 1024, 768));&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   44&lt;/span&gt;             &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt; thumb = ResizeBitmap(bm, 200, 150);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   45&lt;/span&gt;             thumb.Save(file.ToString(), &lt;span style="COLOR: #2b91af"&gt;ImageFormat&lt;/span&gt;.Jpeg);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   46&lt;/span&gt;         }&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   47&lt;/span&gt;         &lt;span style="COLOR: blue"&gt;catch&lt;/span&gt; (&lt;span style="COLOR: #2b91af"&gt;Exception&lt;/span&gt; e)&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   48&lt;/span&gt;         {&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   49&lt;/span&gt;             System.IO.&lt;span style="COLOR: #2b91af"&gt;StreamWriter&lt;/span&gt; y= System.IO.&lt;span style="COLOR: #2b91af"&gt;File&lt;/span&gt;.CreateText(file.ToString()+&lt;span style="COLOR: #a31515"&gt;".txt"&lt;/span&gt;);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   50&lt;/span&gt;             y.WriteLine(e.Message +&lt;span style="COLOR: #2b91af"&gt;Environment&lt;/span&gt;.NewLine+ e.Source); &lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   51&lt;/span&gt;             y.Flush() ;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   52&lt;/span&gt;             y.Close();&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   53&lt;/span&gt;         }&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   54&lt;/span&gt;     }&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   55&lt;/span&gt; &lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   56&lt;/span&gt;     &lt;span style="COLOR: blue"&gt;private&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt; ResizeBitmap(System.Drawing.&lt;span style="COLOR: #2b91af"&gt;Image&lt;/span&gt; originalImage, &lt;span style="COLOR: blue"&gt;int&lt;/span&gt; width, &lt;span style="COLOR: blue"&gt;int&lt;/span&gt; height)&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   57&lt;/span&gt;     {&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   58&lt;/span&gt;         &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt; result = &lt;span style="COLOR: blue"&gt;new&lt;/span&gt; &lt;span style="COLOR: #2b91af"&gt;Bitmap&lt;/span&gt;(width, height);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   59&lt;/span&gt; &lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   60&lt;/span&gt;         &lt;span style="COLOR: blue"&gt;using&lt;/span&gt; (&lt;span style="COLOR: #2b91af"&gt;Graphics&lt;/span&gt; g = &lt;span style="COLOR: #2b91af"&gt;Graphics&lt;/span&gt;.FromImage(result))&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   61&lt;/span&gt;             g.DrawImage(originalImage, 0, 0, width, height);&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   62&lt;/span&gt;         &lt;span style="COLOR: blue"&gt;return&lt;/span&gt; result;&lt;/p&gt;
&lt;p style="MARGIN: 0px"&gt;&lt;span style="COLOR: #2b91af"&gt;   63&lt;/span&gt;     }&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;&lt;!--EndFragment--&gt;Thats it - metoden virker, og skal blot integreres i min sitemap. Bemærk i øvrigt at ASP processen skal havde rettigheder til at browse på de requestede sider, for ellers får koden timeout.&lt;/p&gt;
&lt;p&gt;Code on...&lt;/p&gt;&lt;img src ="http://activedeveloper.dk/weblogs/mrjs/aggbug/6886.aspx" width = "1" height = "1" /&gt;</description><body xmlns="http://www.w3.org/1999/xhtml"><p>Jeg fik stille udfordringen at lave et sitemap med thumbnails af de enkelte sider i et website ;-)</p>
<p>Da jeg er nørd, er den pragmatiske løsning med at grabbe alle sider i websitet med [Ctrl+Alt+Print Screen], ikke aktuel. Nej opgaven skriger på en dynamisk løsning - for hvad nu hvis indholdet skifter - så er der jo ikke andet at gøre end at starte Photoshop op og cutte/resize - hmmm.</p>
<p>Løsningen kunne være at lave en tabel med en masse IFrames der var scaleret med Zoom css-egenskaben - stadig pragmatisk, og hulens langsom i realiteten.</p>
<p>Nej - jeg valgte at gå på strandhugst i <em>Windows.Forms </em>namespacet. I .NET 2.0, er der blandt andet dukket en WebBrowser-kontrol op, og den er lige hvad vi har brug for. Specielt har jeg hæftet mig ved metoden: <em>DrawToBitmap</em> - den må kunne bruges. Så igang med at lave en testside der indeholder en <em>TextBox, Button</em> og en <em>Image</em>-kontrol. Click-eventen på knappen skal så tage den indtastede Url og bruge WebBrowser objektet til at downloade alt indholdet, og så skrive en Jpg-fil som Image-kontrollen så referere til. Simpelt - lidt.</p>
<p>Det første man lige skal bide mærke i, er at WebBrowser kontrollen ikke befinder sig i eet af standard webapplikations referencerne, men i <em>System.Windows.Forms</em>, så man skal lige indsætte en reference til dll'en i sit webprojekt først. Min code-behind endte op med at se sådan ud: </p>
<div style="FONT-SIZE: 10pt; BACKGROUND: white; COLOR: black; FONT-FAMILY: Consolas">
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   18</span>     <span style="COLOR: #2b91af">WebBrowser</span> wb;</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   19</span>     <span style="COLOR: blue">protected</span> <span style="COLOR: blue">void</span> Button1_Click(<span style="COLOR: blue">object</span> sender, <span style="COLOR: #2b91af">EventArgs</span> e)</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   20</span>     {</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   21</span>         wb = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">WebBrowser</span>();</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   22</span>         wb.ScrollBarsEnabled = <span style="COLOR: blue">false</span>;</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   23</span>         wb.Size = <span style="COLOR: blue">new</span> System.Drawing.<span style="COLOR: #2b91af">Size</span>(1024, 768);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   24</span>         wb.DocumentCompleted += <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">WebBrowserDocumentCompletedEventHandler</span>(wb_DocumentCompleted);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   25</span>         wb.Navigate(inpUrl.Text);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   26</span>     }</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   27</span> </p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   28</span>     <span style="COLOR: blue">void</span> wb_DocumentCompleted(<span style="COLOR: blue">object</span> sender, <span style="COLOR: #2b91af">WebBrowserDocumentCompletedEventArgs</span> e)</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   29</span>     {</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   30</span>         <span style="COLOR: #2b91af">Bitmap</span> thumb = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">Bitmap</span>(102, 76);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   31</span>         <span style="COLOR: #2b91af">Bitmap</span> bm = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">Bitmap</span>(1024, 768);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   32</span>         wb.DrawToBitmap(bm, <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">Rectangle</span>(0, 0, 1024, 768));</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   33</span>         <span style="COLOR: blue">string</span> filename = <span style="COLOR: blue">string</span>.Format(<span style="COLOR: #a31515">"~/{0:N}.jpg"</span>, <span style="COLOR: #2b91af">Guid</span>.NewGuid());</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   34</span> </p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   35</span>         bm.Save(Server.MapPath(filename));</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   36</span>         Image1.ImageUrl = filename;</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   37</span>         bm.Dispose();</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   38</span>     }</p></div>
<p><!--EndFragment-->Det virker dog ikke til min store overraskelse....!</p>
<p>Jeg får fejlmeddlelsen:</p>
<p><i>ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.</i></p>
<p>Hvad går der galt? Jo - WebBrowser kontrollen er jo et com-objekt, der har fået en .NET wrapper, og som et sådan, er vi nød til at instantierer, Navigerer, gemme og destruerer i samme tråd. Som det fremgår af ovenstående kode, så er der hooket op på <em>DocumentComplete</em>ev-enten, som eksekveres i en anden tråd. Løsningen ligger i at putte al koden ind i en metode der eksekveres i en separat tråd. </p>
<p>For at vi kan være sikker på at WebBrowser objektet har downloadet al indholdet, er man i stedet for nød til at lave et loop der holder hovedtråden ind til Url'en er helt hentet. Med lidt omskrivning kom jeg frem til følgende funktionelle kode ;-)</p>
<div style="FONT-SIZE: 10pt; BACKGROUND: white; COLOR: black; FONT-FAMILY: Consolas">
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   19</span>     <span style="COLOR: blue">protected</span> <span style="COLOR: blue">void</span> Button1_Click(<span style="COLOR: blue">object</span> sender, <span style="COLOR: #2b91af">EventArgs</span> e)</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   20</span>     {</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   21</span>         <span style="COLOR: blue">string</span> filename = <span style="COLOR: blue">string</span>.Format(<span style="COLOR: #a31515">"~/TN/{0:N}.jpg"</span>, <span style="COLOR: #2b91af">Guid</span>.NewGuid());</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   22</span>         <span style="COLOR: #2b91af">Thread</span> thread = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">Thread</span>(<span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">ParameterizedThreadStart</span>(GetUrl));</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   23</span>         thread.SetApartmentState(<span style="COLOR: #2b91af">ApartmentState</span>.STA);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   24</span>         thread.Start(Server.MapPath(filename));</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   25</span>         Image1.ImageUrl = filename;</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   26</span>     }</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   27</span> </p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   28</span>     <span style="COLOR: blue">private</span> <span style="COLOR: blue">void</span> GetUrl(<span style="COLOR: blue">object</span> file)</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   29</span>     {</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   30</span>         <span style="COLOR: blue">try</span></p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   31</span>         {</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   32</span>             <span style="COLOR: #2b91af">WebBrowser</span> wb = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">WebBrowser</span>();</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   33</span>             wb.ScrollBarsEnabled = <span style="COLOR: blue">false</span>;</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   34</span>             wb.Size = <span style="COLOR: blue">new</span> System.Drawing.<span style="COLOR: #2b91af">Size</span>(1024, 768);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   35</span>             wb.Navigate(inpUrl.Text);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   36</span>             <span style="COLOR: green">//Vent til dokument er hentet</span></p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   37</span>             <span style="COLOR: blue">while</span> (wb.ReadyState != <span style="COLOR: #2b91af">WebBrowserReadyState</span>.Complete)</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   38</span>             {</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   39</span>                 System.Windows.Forms.<span style="COLOR: #2b91af">Application</span>.DoEvents();</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   40</span>             }</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   41</span> </p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   42</span>             <span style="COLOR: #2b91af">Bitmap</span> bm = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">Bitmap</span>(1024, 768);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   43</span>             wb.DrawToBitmap(bm, <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">Rectangle</span>(0, 0, 1024, 768));</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   44</span>             <span style="COLOR: #2b91af">Bitmap</span> thumb = ResizeBitmap(bm, 200, 150);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   45</span>             thumb.Save(file.ToString(), <span style="COLOR: #2b91af">ImageFormat</span>.Jpeg);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   46</span>         }</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   47</span>         <span style="COLOR: blue">catch</span> (<span style="COLOR: #2b91af">Exception</span> e)</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   48</span>         {</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   49</span>             System.IO.<span style="COLOR: #2b91af">StreamWriter</span> y= System.IO.<span style="COLOR: #2b91af">File</span>.CreateText(file.ToString()+<span style="COLOR: #a31515">".txt"</span>);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   50</span>             y.WriteLine(e.Message +<span style="COLOR: #2b91af">Environment</span>.NewLine+ e.Source); </p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   51</span>             y.Flush() ;</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   52</span>             y.Close();</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   53</span>         }</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   54</span>     }</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   55</span> </p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   56</span>     <span style="COLOR: blue">private</span> <span style="COLOR: #2b91af">Bitmap</span> ResizeBitmap(System.Drawing.<span style="COLOR: #2b91af">Image</span> originalImage, <span style="COLOR: blue">int</span> width, <span style="COLOR: blue">int</span> height)</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   57</span>     {</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   58</span>         <span style="COLOR: #2b91af">Bitmap</span> result = <span style="COLOR: blue">new</span> <span style="COLOR: #2b91af">Bitmap</span>(width, height);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   59</span> </p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   60</span>         <span style="COLOR: blue">using</span> (<span style="COLOR: #2b91af">Graphics</span> g = <span style="COLOR: #2b91af">Graphics</span>.FromImage(result))</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   61</span>             g.DrawImage(originalImage, 0, 0, width, height);</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   62</span>         <span style="COLOR: blue">return</span> result;</p>
<p style="MARGIN: 0px"><span style="COLOR: #2b91af">   63</span>     }</p></div>
<p><!--EndFragment-->Thats it - metoden virker, og skal blot integreres i min sitemap. Bemærk i øvrigt at ASP processen skal havde rettigheder til at browse på de requestede sider, for ellers får koden timeout.</p>
<p>Code on...</p><img src ="http://activedeveloper.dk/weblogs/mrjs/aggbug/6886.aspx" width = "1" height = "1" /></body></item><item><dc:creator>Jesper Jensen</dc:creator><title>Drag'n drop med GDI+</title><link>http://activedeveloper.dk/weblogs/mrjs/archive/2006/02/07/2312.aspx</link><pubDate>Tue, 07 Feb 2006 22:28:00 GMT</pubDate><guid>http://activedeveloper.dk/weblogs/mrjs/archive/2006/02/07/2312.aspx</guid><wfw:comment>http://activedeveloper.dk/weblogs/mrjs/comments/2312.aspx</wfw:comment><comments>http://activedeveloper.dk/weblogs/mrjs/archive/2006/02/07/2312.aspx#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://activedeveloper.dk/weblogs/mrjs/comments/commentRss/2312.aspx</wfw:commentRss><trackback:ping>http://activedeveloper.dk/weblogs/mrjs/services/trackbacks/2312.aspx</trackback:ping><description>&lt;p&gt;Der er, i min igangværende applikation, opstået et behov for en &lt;em&gt;PictureBox&lt;/em&gt; med lidt ekstra funktionalitet. Jeg har bl.a. behov for at kunne træskke rundt med dele af det aktuelle billede (&lt;em&gt;Bitmap&lt;/em&gt;). Jeg arbejdede hele dagen med en ide om at trække rundt med en &lt;em&gt;UserControl&lt;/em&gt;, og gentegen billedet så snart &lt;em&gt;MouseUp&lt;/em&gt;-hændelsen indtraf.&lt;/p&gt;
&lt;p&gt;Jeg fik det faktisk til at virke, men det &lt;em&gt;flikkede&lt;/em&gt; grelt på skærmen, når opløsningen var høj.. Ikke lige det min kunde ønskede sig (tror jeg). Så var det jeg kom til at spekulerer lidt på at overtage styringen af alt tegenarbejdet. Følgende kode er skrevet nødtørftigt på min dårlige hjemme pc, så ikke for hår med kritikken - jeg poster mest koden for at kunne få adgang til den i morgen...eller en anden dag jeg skal lave d'n d. Bemærk at jeg kastede en skalering ind i gøjemøget (&lt;em&gt;comboBox1&lt;/em&gt;), bare for at gøre det helt umuligt....&lt;/p&gt;&lt;textarea style="WIDTH: 100%" rows="15"&gt;using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            boxes = new Rectangle[]{
                new Rectangle(100, 200, 100, 50),
                new Rectangle(100, 100, 100, 50),
                new Rectangle(100, 0, 100, 50),
                new Rectangle(0, 200, 100, 50),
                new Rectangle(0, 100, 100, 50),
                new Rectangle(0, 0, 100, 50)
                };
            comboBox1.DataSource = zooms;
        }

        private Rectangle[] boxes;
        private Brush dragBrush = new SolidBrush(Color.FromArgb(80, Color.Red));
        private float[] zooms = new float[] {2f, 1f, 0.5f, 0.25f}; 
        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            float scaling = (float)comboBox1.SelectedItem;
            Graphics g = e.Graphics;
            g.ScaleTransform(scaling, scaling);
            for (int i = 0; i &amp;lt; boxes.Length; i++)
            {
                if (i != selectedBox)
                {
                    g.FillRectangle(Brushes.Yellow, boxes[i]);
                    g.DrawRectangle(Pens.Blue, boxes[i]);
                }
            }
            if (isDragging)
            {
                g.FillRectangle(dragBrush, dragBox);
            }
        }

        private bool isDragging = false;
        private Point mouseDelta;
        private Rectangle dragBox;
        private int selectedBox;

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            Point sp = screenToModel(e.Location);
            for (int i = boxes.Length - 1;i&amp;gt;-1 ; i--)
            {
                if (boxes[i].Contains(sp))
                {
                    dragBox = new Rectangle(boxes[i].Location, boxes[i].Size);
                    isDragging = true;
                    mouseDelta = new Point((sp.X - boxes[i].X), (sp.Y - boxes[i].Y));
                    pictureBox1.Invalidate();
                    selectedBox = i;
                    break;
                }
            }
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                Point sp = screenToModel(e.Location);
                sp.X -= mouseDelta.X;
                sp.Y -= mouseDelta.Y;
                dragBox.Location = sp;
                pictureBox1.Invalidate();
            }
        }

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                isDragging = false;
                boxes[selectedBox].Location = dragBox.Location;
                selectedBox = -1;
                pictureBox1.Invalidate();
            }
        }

        private Point screenToModel(Point mouseLocation)
        {
            float scaling = (float)comboBox1.SelectedItem;
            float x = mouseLocation.X / scaling;
            float y = mouseLocation.Y / scaling;
            return new Point((int)x, (int)y);            
        } 

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            pictureBox1.Invalidate();
        }
    }
}&lt;/textarea&gt; 
&lt;p&gt;I al sin enkelthed kan man splitte &lt;em&gt;Drag'n drop&lt;/em&gt; kode op i følgende bestanddele:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eventhandler til håndtering af &lt;em&gt;Paint&lt;/em&gt; - tegner alle bestanddele i kontrollen 
&lt;/li&gt;&lt;li&gt;Håndtering af &lt;em&gt;MouseDown&lt;/em&gt; skal sætte en global indikator for at der nu 'trækkes', samtidig skal der dannes et pladsholder område 
&lt;/li&gt;&lt;li&gt;Håndtering af &lt;em&gt;MouseMove&lt;/em&gt; skal i tilfælde af at der 'trækkes': opdaterer pladsholderens lokation 
&lt;/li&gt;&lt;li&gt;Håndtering af &lt;em&gt;MouseUp&lt;/em&gt; skal i tilfælde af at der 'trækkes': ændre status til 'der trækkes ikke' og opdaterer den 'trukne' bestanddel 
&lt;/li&gt;&lt;li&gt;&lt;em&gt;Invalider&lt;/em&gt; kontrollen og gennemtving &lt;em&gt;Paint&lt;/em&gt; i de to sidste EventHandere &lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Ovenstående kode skal forbedres i &lt;em&gt;Paint&lt;/em&gt;-handleren, så den kun gentegner de boxe der er blevet invalideret, og &lt;em&gt;MouseMove &amp;amp; MouseUp&lt;/em&gt; skal kun invaliderer det område der er berørt af flytningen (noget med at tracke deltaMouseMove) - det gør jeg i morgen...&lt;/p&gt;
&lt;p&gt;Code on&lt;/p&gt;
&lt;p&gt;&lt;span style="COLOR: #ff0000"&gt;Opdatering 8. februar 2006&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="COLOR: #000000"&gt;Det viste sig at det var fremgangsmåden jeg skulle gå efter. Det kører helt glat og flimmerløst (selv uden optimeret invalidering). Det er åbenbart forbundet med overordentligt meget overhead at bruge &lt;em&gt;CustomControl&lt;/em&gt; som pladsholder i min drag'n drop kontrol. Her er et sd af mit seneste resultat (den røde box er den jeg 'slæber' rundt med, men på dumpet ses musemarkøren ikke - mærkeligt).&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="COLOR: #000000"&gt;&lt;/span&gt;&lt;img title="TransparentBox.JPG" alt="TransparentBox.JPG" src="http://www.onh3.dk/App_blog/TransparentBox.jpg" border="0" jname="TransparentBox.JPG" /&gt;&lt;/p&gt;&lt;img src ="http://activedeveloper.dk/weblogs/mrjs/aggbug/2312.aspx" width = "1" height = "1" /&gt;</description><body xmlns="http://www.w3.org/1999/xhtml"><p>Der er, i min igangværende applikation, opstået et behov for en <em>PictureBox</em> med lidt ekstra funktionalitet. Jeg har bl.a. behov for at kunne træskke rundt med dele af det aktuelle billede (<em>Bitmap</em>). Jeg arbejdede hele dagen med en ide om at trække rundt med en <em>UserControl</em>, og gentegen billedet så snart <em>MouseUp</em>-hændelsen indtraf.</p>
<p>Jeg fik det faktisk til at virke, men det <em>flikkede</em> grelt på skærmen, når opløsningen var høj.. Ikke lige det min kunde ønskede sig (tror jeg). Så var det jeg kom til at spekulerer lidt på at overtage styringen af alt tegenarbejdet. Følgende kode er skrevet nødtørftigt på min dårlige hjemme pc, så ikke for hår med kritikken - jeg poster mest koden for at kunne få adgang til den i morgen...eller en anden dag jeg skal lave d'n d. Bemærk at jeg kastede en skalering ind i gøjemøget (<em>comboBox1</em>), bare for at gøre det helt umuligt....</p><textarea style="WIDTH: 100%" rows="15">using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            boxes = new Rectangle[]{
                new Rectangle(100, 200, 100, 50),
                new Rectangle(100, 100, 100, 50),
                new Rectangle(100, 0, 100, 50),
                new Rectangle(0, 200, 100, 50),
                new Rectangle(0, 100, 100, 50),
                new Rectangle(0, 0, 100, 50)
                };
            comboBox1.DataSource = zooms;
        }

        private Rectangle[] boxes;
        private Brush dragBrush = new SolidBrush(Color.FromArgb(80, Color.Red));
        private float[] zooms = new float[] {2f, 1f, 0.5f, 0.25f}; 
        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            float scaling = (float)comboBox1.SelectedItem;
            Graphics g = e.Graphics;
            g.ScaleTransform(scaling, scaling);
            for (int i = 0; i &lt; boxes.Length; i++)
            {
                if (i != selectedBox)
                {
                    g.FillRectangle(Brushes.Yellow, boxes[i]);
                    g.DrawRectangle(Pens.Blue, boxes[i]);
                }
            }
            if (isDragging)
            {
                g.FillRectangle(dragBrush, dragBox);
            }
        }

        private bool isDragging = false;
        private Point mouseDelta;
        private Rectangle dragBox;
        private int selectedBox;

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            Point sp = screenToModel(e.Location);
            for (int i = boxes.Length - 1;i&gt;-1 ; i--)
            {
                if (boxes[i].Contains(sp))
                {
                    dragBox = new Rectangle(boxes[i].Location, boxes[i].Size);
                    isDragging = true;
                    mouseDelta = new Point((sp.X - boxes[i].X), (sp.Y - boxes[i].Y));
                    pictureBox1.Invalidate();
                    selectedBox = i;
                    break;
                }
            }
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                Point sp = screenToModel(e.Location);
                sp.X -= mouseDelta.X;
                sp.Y -= mouseDelta.Y;
                dragBox.Location = sp;
                pictureBox1.Invalidate();
            }
        }

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                isDragging = false;
                boxes[selectedBox].Location = dragBox.Location;
                selectedBox = -1;
                pictureBox1.Invalidate();
            }
        }

        private Point screenToModel(Point mouseLocation)
        {
            float scaling = (float)comboBox1.SelectedItem;
            float x = mouseLocation.X / scaling;
            float y = mouseLocation.Y / scaling;
            return new Point((int)x, (int)y);            
        } 

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            pictureBox1.Invalidate();
        }
    }
}</textarea> 
<p>I al sin enkelthed kan man splitte <em>Drag'n drop</em> kode op i følgende bestanddele:</p>
<ul>
<li>Eventhandler til håndtering af <em>Paint</em> - tegner alle bestanddele i kontrollen 
</li><li>Håndtering af <em>MouseDown</em> skal sætte en global indikator for at der nu 'trækkes', samtidig skal der dannes et pladsholder område 
</li><li>Håndtering af <em>MouseMove</em> skal i tilfælde af at der 'trækkes': opdaterer pladsholderens lokation 
</li><li>Håndtering af <em>MouseUp</em> skal i tilfælde af at der 'trækkes': ændre status til 'der trækkes ikke' og opdaterer den 'trukne' bestanddel 
</li><li><em>Invalider</em> kontrollen og gennemtving <em>Paint</em> i de to sidste EventHandere </li></ul>
<p>Ovenstående kode skal forbedres i <em>Paint</em>-handleren, så den kun gentegner de boxe der er blevet invalideret, og <em>MouseMove &amp; MouseUp</em> skal kun invaliderer det område der er berørt af flytningen (noget med at tracke deltaMouseMove) - det gør jeg i morgen...</p>
<p>Code on</p>
<p><span style="COLOR: #ff0000">Opdatering 8. februar 2006</span></p>
<p><span style="COLOR: #000000">Det viste sig at det var fremgangsmåden jeg skulle gå efter. Det kører helt glat og flimmerløst (selv uden optimeret invalidering). Det er åbenbart forbundet med overordentligt meget overhead at bruge <em>CustomControl</em> som pladsholder i min drag'n drop kontrol. Her er et sd af mit seneste resultat (den røde box er den jeg 'slæber' rundt med, men på dumpet ses musemarkøren ikke - mærkeligt).</span></p>
<p><span style="COLOR: #000000"></span><img title="TransparentBox.JPG" alt="TransparentBox.JPG" src="http://www.onh3.dk/App_blog/TransparentBox.jpg" border="0" jname="TransparentBox.JPG" /></p><img src ="http://activedeveloper.dk/weblogs/mrjs/aggbug/2312.aspx" width = "1" height = "1" /></body></item><item><dc:creator>Jesper Jensen</dc:creator><title>Progressbar i DataGridView (.NET 2.0)</title><link>http://activedeveloper.dk/weblogs/mrjs/archive/2005/11/17/2091.aspx</link><pubDate>Thu, 17 Nov 2005 12:24:00 GMT</pubDate><guid>http://activedeveloper.dk/weblogs/mrjs/archive/2005/11/17/2091.aspx</guid><wfw:comment>http://activedeveloper.dk/weblogs/mrjs/comments/2091.aspx</wfw:comment><comments>http://activedeveloper.dk/weblogs/mrjs/archive/2005/11/17/2091.aspx#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://activedeveloper.dk/weblogs/mrjs/comments/commentRss/2091.aspx</wfw:commentRss><trackback:ping>http://activedeveloper.dk/weblogs/mrjs/services/trackbacks/2091.aspx</trackback:ping><description>&lt;p&gt;Jeg arbejder på en opgave, hvor jeg skal præsenterer data i en &lt;em&gt;DataGrid&lt;/em&gt;, og hvor een af kolonnerne er en værdi som er udtryk for et forbrug i procent (0-100), som kunden gerne vil havde udtrykt som en vandret søjle (Progressbar). Hvordan kan man så lige få det til at ske? Som altid, så har jeg luret lidt på en anden løsning, for at få en idee til løsningen af mit problem. Hjemmesiden WindowsForms.net har en række færdige applikationer, som man kan downloade og bruge til inspiration. Een af applikationerne er &lt;a title="TaskVision projekt" href="http://www.windowsforms.net/Applications/application.aspx?PageID=20&amp;amp;tabindex=8"&gt;TaskVision&lt;/a&gt; - og ud fra screendump, kan jeg se at det er nogenlunde det resultat jeg vil havde.&lt;/p&gt;&lt;img title="TasVision dump" src="http://www.3wp.dk/WeblogFiles/TaskVisionClient_Thumbnail.jpg" border="0" /&gt; 
&lt;p&gt;Desværre er TaskVision skrevt til .NET 1.1, og desværre bruger den DataGrid til præsentationen. Af een eller anden grund er &lt;em&gt;DataGrid&lt;/em&gt; ikke længere en del af VS.NET's værktøjskasse. Den er blevet udskiftet med et nyt medlem af familien: &lt;em&gt;DataGridView&lt;/em&gt;. Dette nye medlem har ikke en &lt;em&gt;TableStyle&lt;/em&gt; kollektion, og man kan derfor ikke bruge samme fremgangsmåde som i TaskVision - jeg kunne jo ikke finde på at kode mod DataGrid, når den nu er blevet sat ud i kulden (er der nogen der ved hvorfor?), så min løsning kommer til at tage udgangspunkt i &lt;em&gt;DataGridView&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Desværre er der ikke mulighed for at vælge en kolonnetype (ColumnType) af ProgressBar eller lignende - kun TextBox, Link, Button, CheckBox ListBox og Image. Vi er altså nød til selv at tegen søjlen...&lt;/p&gt;
&lt;p&gt;Heldigvis er der en lang række events i DataGridView, som man kan hooke op på, og det ser ud til at &lt;em&gt;CellPainting&lt;/em&gt; er et godt sted at starte - den indtræder for hver celle, når den skal tegnes. Eventhandleren som man laver til denne håndtering, modtager 2 parametre: &lt;em&gt;object&lt;/em&gt; og &lt;em&gt;DataGridViewCellPaintingEventArgs&lt;/em&gt;. Den sidste er selvfølgelig den mest interessante. Man skal huske at man, ved at overtage tegningen af en celle, kommer til at stå for alt det grafiske i det clip, som dækkes af cellen - både rammer og baggrund. Jeg ønsker kun at tegne en søjle, så det er ret smart at event argumentet har en metode: &lt;em&gt;PaintBackground&lt;/em&gt;, som netop tegner ramme og baggrund - man skal så ikke ud i at tegne streger og så videre.&lt;/p&gt;&lt;code&gt;e.PaintBackground(e.CellBounds,true);&lt;/code&gt; 
&lt;p&gt;Den sidste boolske parameter angiver om man ønsker at tegne valgte celler med standard baggrundsfarve, eller om man vil tegne med selected-style. Ud over denne metode, har man selvfølgelig også adgang til et &lt;em&gt;Graphics&lt;/em&gt; objekt, og det er hermed meget nemt at tegne geometriske former så som boxe og streger:&lt;/p&gt;&lt;code&gt;e.Graphics.FillRectangle(&lt;br /&gt;new SolidBrush(Color.Green), &lt;br /&gt;e.CellBounds.X, &lt;br /&gt;e.CellBounds.Y + 4, &lt;br /&gt;Convert.ToInt32((percentage * (float)(e.CellBounds.Width-1f))), &lt;br /&gt;e.CellBounds.Height - 8);&lt;br /&gt;e.Graphics.DrawString((percentage * 100).ToString() + "%", e.CellStyle.Font, Brushes.Black, e.CellBounds.X + 6, e.CellBounds.Y + 4);&lt;br /&gt;&lt;/code&gt;
&lt;p&gt;Koden skulle være selvforklarende - jeg tegner en box med den procentvise længde, og skriver værdien som en streng ovenpå. Den komplette handler ser således ud:&lt;/p&gt;&lt;textarea style="WIDTH: 100%" rows="15"&gt;        private void loadingDockGrid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
        {
            if (this.loadingDockGrid.Columns["Perc"].Index == e.ColumnIndex &amp;amp;&amp;amp; e.RowIndex &amp;gt;= 0)
            {
                float percentage = (float)e.Value;

                e.PaintBackground(e.CellBounds,true);

                e.Graphics.FillRectangle(
                    new SolidBrush(Color.Green), 
                    e.CellBounds.X, 
                    e.CellBounds.Y + 4, 
                    Convert.ToInt32((percentage * (float)(e.CellBounds.Width-1f))), 
                    e.CellBounds.Height - 8);
                e.Graphics.DrawString((percentage * 100).ToString() + "%", e.CellStyle.Font, Brushes.Black, e.CellBounds.X + 6, e.CellBounds.Y + 4);

                e.Handled = true;
            }
&lt;/textarea&gt; 
&lt;p&gt;Man skal, som det fremgår af koden, lige huske at checke for to ting inden man tegner: Er det den rigtige kolonne (her er det "Perc" jeg checker på), og at man befinder sig i en række og ikke i en kolonneoverskrift (e.RowIndex&amp;gt;=0). Læg også mærke til at jeg sætter &lt;em&gt;e.Handled&lt;/em&gt;, for at markerer at jeg har sørget for tegningen - det skal DataGridView springe over...&lt;br /&gt;Det er hvad der skal til for a lave en kolonne med et søjlediagram - man kan selvfølgelig lave alle mulige fede ting, så som at udfylde søjlen med bitmap eller andre effekter. Man har alt fra GDI+ til sin rådighed via Graphics objektet.&lt;/p&gt;
&lt;p&gt;Code on...&lt;/p&gt;&lt;img title="Mine søjler" height="216" src="http://www.3wp.dk/WeblogFiles/ProgressBar.JPG" width="371" border="0" /&gt;&lt;img src ="http://activedeveloper.dk/weblogs/mrjs/aggbug/2091.aspx" width = "1" height = "1" /&gt;</description><body xmlns="http://www.w3.org/1999/xhtml"><p>Jeg arbejder på en opgave, hvor jeg skal præsenterer data i en <em>DataGrid</em>, og hvor een af kolonnerne er en værdi som er udtryk for et forbrug i procent (0-100), som kunden gerne vil havde udtrykt som en vandret søjle (Progressbar). Hvordan kan man så lige få det til at ske? Som altid, så har jeg luret lidt på en anden løsning, for at få en idee til løsningen af mit problem. Hjemmesiden WindowsForms.net har en række færdige applikationer, som man kan downloade og bruge til inspiration. Een af applikationerne er <a title="TaskVision projekt" href="http://www.windowsforms.net/Applications/application.aspx?PageID=20&amp;tabindex=8">TaskVision</a> - og ud fra screendump, kan jeg se at det er nogenlunde det resultat jeg vil havde.</p><img title="TasVision dump" src="http://www.3wp.dk/WeblogFiles/TaskVisionClient_Thumbnail.jpg" border="0" /> 
<p>Desværre er TaskVision skrevt til .NET 1.1, og desværre bruger den DataGrid til præsentationen. Af een eller anden grund er <em>DataGrid</em> ikke længere en del af VS.NET's værktøjskasse. Den er blevet udskiftet med et nyt medlem af familien: <em>DataGridView</em>. Dette nye medlem har ikke en <em>TableStyle</em> kollektion, og man kan derfor ikke bruge samme fremgangsmåde som i TaskVision - jeg kunne jo ikke finde på at kode mod DataGrid, når den nu er blevet sat ud i kulden (er der nogen der ved hvorfor?), så min løsning kommer til at tage udgangspunkt i <em>DataGridView</em>.</p>
<p>Desværre er der ikke mulighed for at vælge en kolonnetype (ColumnType) af ProgressBar eller lignende - kun TextBox, Link, Button, CheckBox ListBox og Image. Vi er altså nød til selv at tegen søjlen...</p>
<p>Heldigvis er der en lang række events i DataGridView, som man kan hooke op på, og det ser ud til at <em>CellPainting</em> er et godt sted at starte - den indtræder for hver celle, når den skal tegnes. Eventhandleren som man laver til denne håndtering, modtager 2 parametre: <em>object</em> og <em>DataGridViewCellPaintingEventArgs</em>. Den sidste er selvfølgelig den mest interessante. Man skal huske at man, ved at overtage tegningen af en celle, kommer til at stå for alt det grafiske i det clip, som dækkes af cellen - både rammer og baggrund. Jeg ønsker kun at tegne en søjle, så det er ret smart at event argumentet har en metode: <em>PaintBackground</em>, som netop tegner ramme og baggrund - man skal så ikke ud i at tegne streger og så videre.</p><code>e.PaintBackground(e.CellBounds,true);</code> 
<p>Den sidste boolske parameter angiver om man ønsker at tegne valgte celler med standard baggrundsfarve, eller om man vil tegne med selected-style. Ud over denne metode, har man selvfølgelig også adgang til et <em>Graphics</em> objekt, og det er hermed meget nemt at tegne geometriske former så som boxe og streger:</p><code>e.Graphics.FillRectangle(<br />new SolidBrush(Color.Green), <br />e.CellBounds.X, <br />e.CellBounds.Y + 4, <br />Convert.ToInt32((percentage * (float)(e.CellBounds.Width-1f))), <br />e.CellBounds.Height - 8);<br />e.Graphics.DrawString((percentage * 100).ToString() + "%", e.CellStyle.Font, Brushes.Black, e.CellBounds.X + 6, e.CellBounds.Y + 4);<br /></code>
<p>Koden skulle være selvforklarende - jeg tegner en box med den procentvise længde, og skriver værdien som en streng ovenpå. Den komplette handler ser således ud:</p><textarea style="WIDTH: 100%" rows="15">        private void loadingDockGrid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
        {
            if (this.loadingDockGrid.Columns["Perc"].Index == e.ColumnIndex &amp;&amp; e.RowIndex &gt;= 0)
            {
                float percentage = (float)e.Value;

                e.PaintBackground(e.CellBounds,true);

                e.Graphics.FillRectangle(
                    new SolidBrush(Color.Green), 
                    e.CellBounds.X, 
                    e.CellBounds.Y + 4, 
                    Convert.ToInt32((percentage * (float)(e.CellBounds.Width-1f))), 
                    e.CellBounds.Height - 8);
                e.Graphics.DrawString((percentage * 100).ToString() + "%", e.CellStyle.Font, Brushes.Black, e.CellBounds.X + 6, e.CellBounds.Y + 4);

                e.Handled = true;
            }
</textarea> 
<p>Man skal, som det fremgår af koden, lige huske at checke for to ting inden man tegner: Er det den rigtige kolonne (her er det "Perc" jeg checker på), og at man befinder sig i en række og ikke i en kolonneoverskrift (e.RowIndex&gt;=0). Læg også mærke til at jeg sætter <em>e.Handled</em>, for at markerer at jeg har sørget for tegningen - det skal DataGridView springe over...<br />Det er hvad der skal til for a lave en kolonne med et søjlediagram - man kan selvfølgelig lave alle mulige fede ting, så som at udfylde søjlen med bitmap eller andre effekter. Man har alt fra GDI+ til sin rådighed via Graphics objektet.</p>
<p>Code on...</p><img title="Mine søjler" height="216" src="http://www.3wp.dk/WeblogFiles/ProgressBar.JPG" width="371" border="0" /><img src ="http://activedeveloper.dk/weblogs/mrjs/aggbug/2091.aspx" width = "1" height = "1" /></body></item></channel></rss>