\

Školení Návrhové vzory, OOP a UML


 Thursday, January 28, 2010
C# Posterous API pro Silverlight 4 (a .Net Framework 3.5) – verze 0.0.0.2

Stáhnout C# Posterous API pro Silverlight 4.0

Stáhnout C# Posterous API pro .Net Framework 3.5

Vše podstatné k C# Posterous API naleznete v úvodním článku.

Poznámky ke změnám v této verzi:

  • Kvůli verzi pro Silverlight přidány další asynchronní metody tak, aby bylo možné získat data z webu asynchronně, jak to Silverlight vyžaduje a jak je toto chování v aplikacích vynuceno  asynchronními metodami v rozhraní platformních tříd - příkladem budiž rozhraní tříd WebRequest a WebResponse. Pokud se pokusíte zavolat synchronní verzi metody v SL z UI vlákna, měli byste z Posterous API dostat výjimku – to je lepší varianta, než skončit v paralyzovaném stavu, kdy celá aplikace na nic nereaguje.

Ukázka uložení  změn v blogpostu – přidání komentáře. :


        var comment = post.CreateNewComment(updateText);
        post.SaveChangesCompleted += (_0, _1) =>
                                         {
                                             Assert.IsTrue(comment.Id > 0);
                                             TestComplete();
                                         };
        post.SaveChangesAsync();

  • V Silverlight verzi aplikace nedostanete v událostech rozhraní IRawRequestResponsePublisher k modifikaci objekty HttpWebRequest a HttpWebResponse, které jsou použity pro stažení obrázku autora (IAuthor.AuthorPicture) a stažení média (IPosterousMedium.Medium). Důvodem je, že pro stažení používám svého potomka třídy WebClient. Tento potomek přepisuje metody GetWebRequest a GetWebResponse, které jsou poté nabídnuty v událostech dostupných přes rozhraní IRawRequestResponsePublisher. V Silverlightu je ale třída WebClient označena jako kritický kód (Critical) a takzvaný transparentní  (Transparent) kód, pod který patří i námi psaný kód, nemá právo z takto označené infrastrukturní třídy podědit. Kromě dvou výše zmíněných omezení ale IRawRequestResponsePublisher pracuje stejně jako v desktopové verzi  a v další verzi API uvažuji, že místo WebClienta použiju na stažení obrázku autora i všech dalších médií přímo třídy WebRequest a WebResponse, které budou opět nabídnuty i v rozhraní IRawRequestResponsePublisher.
  • Silverlight má omezený přístup k souborovému systému, a proto naleznete v blogpostu (IPosterousPost) a twitter postu (ITwitterPost) další dvě metody pro přidání médií. Ve verzi 0.0.0.1 bylo možné předat pouze cestu k souboru, nyní můžete předat jakýkoli stream – např. Stream načtený z IsolatedFileStorage.
    Nové metody:
    void AppendMedium(Stream stream, string mediaName, Action<Stream> releaseStreamFunction);
    void AppendMedium(Stream stream, string mediaName);

Kromě streamu a názvu média můžete do jedné z variant funkce AppendMedium předat delegáta typu Action<Stream> , který bude vyvolán v okamžiku, kdy Posterous API  již se streamem nepotřebuje pracovat. Nejasná zodpovědnost, kdo vlastní a hlavně uvolňuje zdroje alokované objektem stream, mě právě vedla k tomu, že v první verzi API pro .Net Framework 3.5 jste mohli pouze předat název souboru a životní cyklus FileStreamu byl zcela pod mou kontrolu. Jestliže delegáta releaseStreamFunction nepředáte, máte životní cyklus streamu zcela ve svých všemocných rukou. Posterous API ani jeho autor si nepřejí být vyzváni na souboj  žádným šíleným omnipotentním vývojářem :-), a proto na předaném streamu Posterous API nikdy nevolá metodu Close ani Dispose.

Ukázka práce s metodou AppendMedium:

IPosterousPost newPost = site.CreatePost(DateTime.Now + " Media Silverlight",
                                                         "Príliš žlutoucký kun úpel dábelské ódy", true);

                IsolatedStorageFileStream stream = new IsolatedStorageFileStream("AlbumArtSmall.jpg ", FileMode.Open, IsolatedStorageFile.GetUserStoreForSite());
                
                newPost.AppendMedium(stream, "mojeSL.jpg", isoStream => isoStream.Close());
                               
                newPost.AddTag("Všechno a nic");
                newPost.AddTag("C# omnia vincit");
                newPost.SaveChangesCompleted += (_, __) =>
                                            {
                                                Assert.IsTrue(newPost.Id > 0);
                                                TestComplete();
                                            };
                newPost.SaveChangesAsync();

Jednoduchá ukázka použití API v Silverlightu. V jednoduchém boxu na stránce zobrazíme titulek spotu a url spotu. Výsledek vypadá takto:

SLBox

 

Ve Visual Studiu jsem založil projekt typu běžná navigační aplikace v Silverlightu. Zde je jednoduchý “ViewModel” pro stránku:

public class VM_Posts : INotifyPropertyChanged
    {
        public static readonly string ASYNC_EXCEPTION_TEXT = "Error while retrieving data. See inner exceptions for details";
        
        private bool m_isBusy;
        private IEnumerable<ViewPost> m_posts;

        public VM_Posts()
        {
            m_posts = null;
            IsBusy = false;
            LinkClickCommand = new DelegateCommand<String>(link => Debug.WriteLine(link) /*m_navigationServices.HandleExternalLink(link)*/,
                                                             _ => true);
            init();
        }

        
        private void init()
        {

                IsBusy = true;
                IPosterousApplication posterousApp = PosterousApplication.Current;
                IPosterousAccount posterousAccount = posterousApp.GetPosterousAccount("<PosterousUserName>", "PosterousPassword>");

                posterousAccount.SitesLoaded += (o, e) =>
                                                    {
                                                        throwIfAsyncEx(e.Exception);
                                                        posterousAccount.PrimarySite.PostsLoaded += (_, e2) =>
                                                                                                        {
                                                                                                            throwIfAsyncEx(e2.Exception);
                                                                                                            Posts = (from p in e2.Value
                                                                                                                    select new ViewPost
                                                                                                                               {
                                                                                                                                   Title = p.Title,
                                                                                                                                   Body = p.Body,
                                                                                                                                   Url = p.Url

                                                                                                                               }).ToList();                                                                                                            
                                                                                                            
                                                                                                        };
                                                        posterousAccount.PrimarySite.LoadAllPostsAsync();
                                                    };



                posterousAccount.LoadSitesAsync();
            
            
        }

       private void throwIfAsyncEx(Exception exception)
        {
            if (exception != null)
            {
                IsBusy = false;
                throw new Exception(ASYNC_EXCEPTION_TEXT,exception);
            }
        }
        
        

        public IEnumerable<ViewPost> Posts
        {
            get
            {
                return m_posts;
            }
            private set
            {
                
                m_posts = value;
                if (m_posts != null)
                {
                    IsBusy = false;
                }
                OnPropertyChanged(new PropertyChangedEventArgs("Posts"));
                
                
            }
        }

        

        public bool IsBusy
        {
            get
            {
                return  m_isBusy;
            }
            private set
            {
                m_isBusy = value;
                OnPropertyChanged(new PropertyChangedEventArgs("IsBusy"));
            }
        }

        public ICommand LinkClickCommand
        {
            get;
            private set;
        }

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        #endregion
    }

    public class ViewPost
    {
        public string Title
        {
            get;
            set;
        }

        public string Body
        {
            get;
            set;
        }
        

        public string Url
        {
            get;
            set;            
        }
    }
}

V privátní metodě init, která je volána ihned po vytvoření instance třídy WM_Posts, nahrajeme blogy (Sites –> metoda LoadSitesAsync) a poté opět asynchronně z primárního blogu (PrimarySite) nahraju všechny blogposty (metoda LoadAllPostsAsync). Jestliže při některém asynchronním volání dojde k výjimce, metoda throwIfAsyncEx výjimku zpropaguje do UI vlákna.

Z nahraných blogpostů jsou vlastností Title, Url a Body přeneseny do instancí pomocné třídy ViewPost a kolekce objektů ViewPost je uložena do vlastnosti Posts. Třídu ViewPost vytvořit musíme, protože “Binding” dat, který budeme používat v XAMLu, nedokáže v Silverlightu z bezpečnostních důvodů pře reflection přistupovat ke člelnům “internal” třídy deklarované v jiné assembly. Třída PosterousPost, která v Posterous API implementuje rozhraní IPosterousPost, je označena jako internal. Ze stejného důvodu v Silverlightu nemohu použít místo explicitně definované třídy ViewPost anonymní datový typ – i anonymní datové typy jsou reprezentovány ve výsledném aplikačním dll třídami s modifikátorem viditelnosti “internal” a kód pro “binding” dat je ve zcela jiné platformní assembly.

Kromě vlastnosti Posts nabízí třída WM_Posts vlastnost IsBusy, kterou v XAMLu  využijeme k zobrazení indikátoru informujícího uživatele, že právě získáváme data. Vystaven je také LinkClickCommand, který nůže být zavolán například v okamžiku, kdy je stisknuto nějaké tlačítko reprezentující hyperlink s URL blogpostu.

A zde je pro úplnost ještě XAML. Památeční XAML, protože mi při jeho psaní nejméně a bez jakékoli nadsázky 50x spadlo Visual Studio 2010 Beta 2 (s Resharperem).:-)

<navigation:Page x:Class="SL_PosterousAPITest.Home"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    xmlns:ct="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
    xmlns:local="clr-namespace:SL_PosterousAPITest;assembly=SL_PosterousAPITest"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
    Title="Home"
    Style="{StaticResource PageStyle}">

    <Grid x:Name="LayoutRoot">
        <Grid.Resources>
            <local:VM_Posts x:Key="PostsView"></local:VM_Posts>            
            <Style x:Name="PostTitleStyle" TargetType="TextBlock">
                <Setter Property="FontWeight" Value="Bold" />
                 <Setter Property="Foreground" Value="IndianRed" />
                <Setter Property="FontSize" Value="14" />
            </Style>
            <Style x:Key="PostsRectangle" TargetType="Rectangle">
                <Setter Property="RadiusX" Value="30" />
                <Setter Property="RadiusY" Value="30" />                
                <Setter Property="Fill">
                    <Setter.Value>
                        <LinearGradientBrush>
                            <GradientStop Offset="0" Color="LightYellow" />
                            <GradientStop Offset="1" Color="LightBlue" />                            
                        </LinearGradientBrush>
                    </Setter.Value>
                </Setter>
            </Style>
            <Style TargetType="ItemsControl">
                <Setter Property="Width" Value="500" />
                <Setter Property="Height" Value="360" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <ScrollViewer BorderThickness="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                                <Grid>                                    
                                    <Rectangle Style="{StaticResource PostsRectangle}">                                        
                                    </Rectangle>
                                    <ItemsPresenter Margin="15,15,0,0"/>
                                    <ct:BusyIndicator IsBusy="{Binding Path=IsBusy}" 
                                                      DisplayAfter="0" 
                                                      HorizontalAlignment="Center" 
                                                      VerticalAlignment="Center"                                                       
                                                      />
                                    
                                    
                                </Grid>
                            </ScrollViewer>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Grid.Resources>        
        <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}">

            <StackPanel x:Name="ContentStackPanel" DataContext="{StaticResource PostsView}">

                <TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"
                                   Text="Home"/>
                <ItemsControl Name="itemsPosts" ItemsSource="{Binding Mode=OneWay, Path=Posts}">                  
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Vertical">
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Style="{StaticResource PostTitleStyle}" Text="{Binding Path=Title}"></TextBlock>                                
                                 </StackPanel>
                                <HyperlinkButton Content="{Binding Path=Url}" 
                                                 Command="{Binding Source={StaticResource PostsView}, Path=LinkClickCommand}"                                                  
                                                 CommandParameter="{Binding  RelativeSource={RelativeSource Self}, Path=Content}"
                                                 FontSize="12"
                                                 ></HyperlinkButton>                                
                            </StackPanel>
                        </DataTemplate>                        
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </StackPanel>

        </ScrollViewer>
    </Grid>

</navigation:Page>


Thursday, January 28, 2010 4:33:08 PM (Central Europe Standard Time, UTC+01:00)       
Comments [0]  .NET Framework | C# Posterous API | Silverlight


 Wednesday, January 20, 2010
Ukázka práce s Posterous API – zálohování blogu

Stáhnout výsledné exe -RSPosterousBackup.

Po jednoduchém přehledu možností mého C# Posterous API wrapperu se nyní podíváme, jak se dá API použít k zálohování vašeho blogu. Pro účely tohoto článku předpokládám, že jste úvodní článek o API wrapperu četli.

Zálohovač blogu (RSPosterousBackup.exe) je jednoduchá konzolová aplikace, které stačí předat uživatelské jméno (parametr –u)  a heslo (parametr –p)  vašeho účtu na Posterous a také adresář vašem počítači (parametr bd), do kterého chcete blog zazálohovat.

Jednoduchá ukázka:

RSPosterousBackup.exe -u:rene@renestein.net -p:mojeheslo -bd:c:\_Archiv\PosterousBackup

Blog uživatelského účtu reneATrenestein.net s heslem mojeheslo bude zazálohován do adresáře c:\_Archiv\PosterousBackup.

Program referencuje samozřejmě knihovnu RStein.Posterous.API a pro (své, uznávám :-)) pobavení jsem také použil RX for .Net Framework 3.5 SP1. Z RX Frameworku jsou referencovány assembly System.CoreEx, System.Interactive a System.Threading. Nejzajímavější na RX Frameworku je, že pro verzi 3.5 .Net Frameworku zpřístupňuje Parallel Linq – PLINQ, který je součástí připravovaného .Net Frameworku 4.0.

Ještě upozornění – v žádném případě nechci tvrdit, že kód, který uvidíte, využívá PLINQ správným způsobem. Program je jen pískovištěm, na kterém jsem si zkoušel a ověřoval, co RX a PLINQ umí a jak vypadá výsledný kód.

Po spuštění RSPosterousBackup.exe funkce Main pouze předá argumenty z příkazové řádky metodě BackupData v třídě BackupEngine, která představuje výkonný mozek celého zálohovače Posterous blogu.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RSPosterousBackup
{
    class Program
    {
        static void Main(string[] args)
        {
            var engine = new BackupEngine();
            engine.BackupData(args);
            Console.ReadLine();
        }
    }
}

 

Zde je třída BackupEngine

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using RStein.Posterous.API;

namespace RSPosterousBackup
{
    public class BackupEngine
    {
        #region constants
        public static readonly string POSTEROUS_USER = "-u";
        public static readonly string POSTEROUS_PASSWORD = "-p";
        public static readonly string BACKUP_DIRECTORY= "-bd";
        public static readonly string HELP_KEY = "-?";
        public static readonly string HELP_ALT_KEY = "-h";
        public static readonly string POSTEROUS_FILE_EXTENSION = ".posterous";
        public static readonly string POSTEROUS_MEDIA_FORMAT = "Media_{0}";
        public static readonly string POSTEROUS_COMMENT_FORMAT = "Comment_{0}";
        public static readonly string POSTEROUS_SITE_FORMAT = "Site_{0}";
        #endregion constants
        #region private variables        
        #endregion private variables

        #region constructors
        public BackupEngine()
        {
        }
        #endregion constructors
        
        public void BackupData(string[] commandLine)
        {
            if (commandLine == null)
            {
                throw new ArgumentNullException("commandLine");
            }

            var parser = new CommandLineParser();
            Dictionary<string, string> cmSwitches = parser.Parse(commandLine);

            Debug.Assert(cmSwitches != null);

            if (cmSwitches.Keys.Any(key => (key.Equals(HELP_KEY, StringComparison.OrdinalIgnoreCase)) ||
                                       (key.Equals(HELP_ALT_KEY,StringComparison.OrdinalIgnoreCase))))
            {
                logMessage(GlobalConstants.CMDLINE_SHOW_USAGE);
                return;
            }

            if (!checkSwitches(cmSwitches))
            {
                logMessage(GlobalConstants.CMDLINE_SHOW_USAGE);
                return;                
            }

            backupDataInner(cmSwitches);
        }

        private void backupDataInner(Dictionary<string, string> cmSwitches)
        {
            string pUser = cmSwitches[POSTEROUS_USER];
            string pPassword = cmSwitches[POSTEROUS_PASSWORD];
            string backupDir = cmSwitches[BACKUP_DIRECTORY];
            try
            {
                IPosterousAccount account = PosterousApplication.Current.GetPosterousAccount(pUser, pPassword);
                if (!Directory.Exists(backupDir))
                {
                    Directory.CreateDirectory(backupDir);
                }

                var currDirBackupName = new StringBuilder(DateTime.Now.ToLocalTime().ToString());
                currDirBackupName.ToSafeFileName();
                
                
                string currentBackupDir = Path.Combine(backupDir, currDirBackupName.ToString());
                account.Sites.Run(site =>
                                     {
                                         string sitePath = Path.Combine(currentBackupDir, String.Format(POSTEROUS_SITE_FORMAT, site.Id.ToString()));
                                         Directory.CreateDirectory(sitePath);
                                     });


                var processedPosts = (from site in account.Sites.AsParallel()
                                      from post in site.Posts.AsParallel()
                                         .Do(postPost =>
                                                  Console.WriteLine(String.Format(GlobalConstants.POST_BACKUP_MESSAGE_FORMAT,
                                                                         postPost.Title,
                                                                         Thread.CurrentThread.ManagedThreadId)))
                                        
                                         .Do(postPost =>
                                                 {                                                  
                                                     string siteDir = Path.Combine(currentBackupDir, String.Format(POSTEROUS_SITE_FORMAT, site.Id.ToString()));
                                                     
                                                     var titlBuilder = new StringBuilder(postPost.Title + postPost.Id.ToString());
                                                     titlBuilder.ToSafeFileName();

                                                     string postDirName = Path.Combine(siteDir,
                                                                                       titlBuilder.ToString()
                                                                                       );
                                                     
                                                     Directory.CreateDirectory(postDirName);
                                                     string postFileName = Path.Combine(postDirName, titlBuilder.ToString() + POSTEROUS_FILE_EXTENSION);
                                                     using (var fileStream = File.Open(postFileName, FileMode.Create))
                                                     using (var stremWriter = new StreamWriter(fileStream, Encoding.UTF8))
                                                     {
                                                         stremWriter.Write(postPost.Body);
                                                     }

                                                     postPost.Media
                                                         .AsParallel()
                                                         .Run(media =>
                                                                 {
                                                                                                             
                                                                     var mediaName = new StringBuilder(String.Format(POSTEROUS_MEDIA_FORMAT, Guid.NewGuid()));
                                                                     mediaName.ToSafeFileName();
                                                                     string mediaFile = Path.Combine(postDirName, mediaName.ToString());


                                                                     using (var fileStream = File.Open(mediaFile, FileMode.Create))
                                                                     {


                                                                         media.Content.CopyToStream(fileStream);

                                                                     }
                                                                 });


                                                     postPost.Comments
                                                         .AsParallel()                                                         
                                                         .Run(comment =>
                                                                 {
                                                                                                             
                                                                     var commentFileName = new StringBuilder(String.Format(POSTEROUS_COMMENT_FORMAT, Guid.NewGuid()));
                                                                     commentFileName.ToSafeFileName();
                                                                     string commentFile = Path.Combine(postDirName, commentFileName.ToString());


                                                                     using (var fileStream = File.Open(commentFile, FileMode.Create))
                                                                     using (var stremWriter = new StreamWriter(fileStream, Encoding.UTF8))
                                                                     {


                                                                         stremWriter.WriteLine(comment.Author.Name);
                                                                         stremWriter.Write(comment.Body);

                                                                     }
                                                                 });
                                                 })

                                     select post).ToList();





                logMessage(String.Format(GlobalConstants.FINAL_BACKUP_MESSAGE_FORMAT, processedPosts.Count));
                
                
            }
            catch (Exception e)
            {
                 Trace.WriteLine(e);
                 Console.WriteLine(e);                 
            }
           
            
        }

        private bool checkSwitches(Dictionary<string, string> cmSwitches)
        {            

            return ckeckIfSwitchNullOrEmpty(POSTEROUS_USER, cmSwitches) &&
                    ckeckIfSwitchNullOrEmpty(POSTEROUS_PASSWORD, cmSwitches) &&
                    ckeckIfSwitchNullOrEmpty(BACKUP_DIRECTORY, cmSwitches);
            
        }

        private bool ckeckIfSwitchNullOrEmpty(string switchKey, Dictionary<string, string> cmSwitches)
        {
            string val;
            cmSwitches.TryGetValue(switchKey, out val);
            if (String.IsNullOrEmpty(val))
            {
                logMessage(String.Format(GlobalConstants.INVALID_CML_SWITCH_FORMAT_STRING_EX, switchKey));
                return false;
            }
            return true;
        }

        private void logMessage(string message)
        {
            
            Console.WriteLine(message);
        }
    }
}

Třída BackupEngine nejdříve v metodě BackupData pomocí instance třídy CommandLineParser rozpársuje příkazový řádek a voláním pomocné metody checkSwitches zkontroluje, zda byly předány všechny vyžadované parametry. Jestliže nějaký parametr chybí nebo byl zadán parametr pro zobrazení nápovědy (-h, –?), program zobrazí nápovědu a k zálohování nedojde.

Ihned po dokončení všech předběžných kontrol je volána privátní metoda backupDataInner, která je odpovědná za zálohování blogu. Metoda backupDataInner získá odkaz na Posterous účet (PosterousApplication.Current.GetPosterousAccount(pUser, pPassword); a poté pro každou Site (samostatný blog) založí nový podadresář v adresáři, jehož názvem je aktuální lokální datum a čas a který je vytvořen v adresáři předaném v parametru –bd uživatelem. 

Adresářová struktura pro každý zálovaný blog:

<adresář určený –bd přepínačem>\<adresář  - názvem je aktuální datum a čas>\Site_<Site Id>

Příklad adresářové struktury:

"c:\_Archiv\PosterousBackup\20.1.2010 15_57_07\Site_851694"

Zálohován tedy není jeden blog, ale všechny blogy, které jsou asociovány s daným posterous účtem.

Můžete si všimnout, že pro založení podaresáře používvám jednu z RX extenzních metod – metodu Run.

                account.Sites.Run(site =>
                                     {
                                         string sitePath = Path.Combine(currentBackupDir, String.Format(POSTEROUS_SITE_FORMAT, site.Id.ToString()));
                                         Directory.CreateDirectory(sitePath);
                                     });

Metoda Run je náhradou za extenzní metodu ForEach, kterou jste si dříve museli sami dopsat nebo jste byli nuceni použít metodu ForEach ve třídě List takto.

account.Sites.ToList().ForEach(site => {//zbytek kódu lambdy identický s kódem výše…}); 



Zdůrazním, že metoda Run vykoná nějaký vedlejší efekt nad každým elementem v IEnumerable, tedy v našem případě založí adresář, a dále již s elementy nepracuje – vrací void. Za chvíli uvidíme metodu, která nám umožní to samé, co metoda Run, ale elementy pošle po “vykonání vedlejšího efektu nad elementem” (tedy spuštění námi předané funkce jakou je např. lambda  pro založení adresáře) ke zpracování dalším LINQ extenzním metodám.

Signatura metody Run:

public static void Run<TSource>(
    this IEnumerable<TSource> source,
    Action<TSource> action
)

Dále do proměnné processedPosts uložíme pomocí speciálního a pro naši kratochvíli jediného LINQ dotazu všechny zpracované blogposty (instance podporující rozhraní IPosterousPost). Jak vidíte, stačí se přes Posterous API dotázat do kolekce Sites (blogy) a poté z každého blogu zpracovat všechny blogspoty.

from site in account.Sites.AsParallel()
                                      from post in site.Posts.AsParallel()…

Na více místech v dotazu si můžete všimnout volání extenzní metody AsParalllel, kterým dáváte najevo, že zpracování jednotlivých blogpostů a také médií (IPosterousMedium) a komentářů (IPosterousComment) může proběhnout ve více vláknech – o detaily se ale postará PLINQ, vy sami žádná nová vlákna nespouštíte ani nespravujete.

Můžete si také všimnout, že na několika místech volám extenzní metodu Do .Metoda Do pracuje podobně jako před chvílí zmiňovaná metoda Run. Na každý element v zdrojové kolekci aplikuje předanou funkci, ale poté na rozdíl od metody Run element předá k dalšímu zpracování.

Signatura metody Do:

public static IEnumerable<TSource> Do<TSource>(
	this IEnumerable<TSource> source,
	Action<TSource> action
)
 

Zde vypíšeme přes RX extenzní metodu Do titulek právě zpracovávaného blogpostu a Id vlákna, které blogspot zpracovává. Tato metoda je zde jen na ukázku, že každý element ve zdrojové kolekci je metodou Do předán dále ke zpracování

 .Do(postPost =>
                                                  Console.WriteLine(String.Format(GlobalConstants.POST_BACKUP_MESSAGE_FORMAT,
                                                                         postPost.Title,
                                                                         Thread.CurrentThread.ManagedThreadId)))

Na metodu Do navazuje další metoda Do, ve které proběhne zpracování každého blogspotu. V adresáří každé Site (blogu) je vytvořen pro každý blogspot vytvořen nový podadresář, jehož názvem je titulek (vlastnost Title) společně s  Id blogpostu. Do tohoto podadresáře je uložen text blogspotu. Soubor s textem blogspotu má příponu posterous a také jsou do podadresáře uložena média (zvukové, obrazové a video soubory) a všechny komentáře k blogspotu.

Hlavní část programu je za námi. Zde jsou ještě výpisy pomocných tříd.

Třída CommandLineParser pro pársování hodnot předaných uživatelem v příkazovém řádku.

using System;
using System.Collections.Generic;
using System.Linq;

namespace RSPosterousBackup
{
    public class CommandLineParser
    {
        #region constants

        public static readonly char COMMAND_PARTS_SEPARATOR = '-';
        public static readonly char COMMAND_VALUE_SEPARATOR = ':';
        public const int MIN_KEY_PARTS = 1;
        public const int MAX_KEY_PARTS = 2;
        #endregion constants
        
        #region constructors
        public CommandLineParser()
        {
        }
        #endregion constructors
        #region methods
        public virtual Dictionary<string, string> Parse (string[] commandLine)
        {
            if (commandLine == null)
            {
                throw new ArgumentNullException("commandLine");
            }
            
            return parseInner(commandLine);
        }

        private Dictionary<string, string> parseInner(string[] commandLine)
        {            
            var dict = (from part in commandLine
                        let keyValuePair = part.Split(new[] {COMMAND_VALUE_SEPARATOR}, MAX_KEY_PARTS)
                        select new
                                   {
                                       Key = keyValuePair.First().Trim().ToLower(),
                                       Value = keyValuePair.Length > MIN_KEY_PARTS ? keyValuePair.Last().Trim() : String.Empty
                                   }).ToDictionary(kv => kv.Key, kv => kv.Value);

            return dict;
        }

        #endregion methods
    }
}

Konstanty

namespace RSPosterousBackup
{
    public static class GlobalConstants
    {
        public static readonly string INVALID_CML_SWITCH_FORMAT_STRING_EX = "Invalid switch {0}";
        public static readonly char SAFE_FILE_PATH_CHAR = '_';        
        public static readonly string POST_BACKUP_MESSAGE_FORMAT = "Processing post: {0}  - in thread {1}";
        public static readonly string FINAL_BACKUP_MESSAGE_FORMAT = "Total posts: {0}";
        public static readonly string CMDLINE_SHOW_USAGE =
@"Usage: 
RSPosterousBackup.exe -u:<posterous user name> p:<posterous password> -bd:<backup directory>
Example:RSPosterousBackup.exe -u:GIC@Roma.com p:Rubicon -bd:c:\PosterousBackup
RSPosterousBackup.exe -?  - show this help";
                                                                 
    }
}

Třída StringBuilderExtensions s extenzní metodou ToSafeFileName, která v navrhovaném jménu souboru nahradí nepovolené znaky podtržítkem.

using System.IO;
using System.Linq;
using System.Text;

namespace RSPosterousBackup
{
    public static class StringBuilderExtensions
    {
        public static void ToSafeFileName(this StringBuilder builder)
        {
            Path.GetInvalidFileNameChars().Run(ch => builder.Replace(ch, GlobalConstants.SAFE_FILE_PATH_CHAR));
        }
    }
}

Stáhnout výsledné exe -RSPosterousBackup.



Wednesday, January 20, 2010 5:54:21 PM (Central Europe Standard Time, UTC+01:00)       
Comments [2]  .NET Framework | C# Posterous API | LINQ | RX Extensions


 Friday, January 15, 2010
Projekt C# Posterous API – verze 0.0.0.1 Alfa

 

Stáhnout knihovnu – download

Jak jsem avizoval minulý týden na twitteru, píšu C# wrapper webového API zajímavé služby Posterous.

Pár odkazů na začátek:

Jestliže nevíte, co je Posterous, přečtěte si článek na Živě.

Popis Posterous API. Hned na začátku zdůrazním, že autoři Posterous API nepovažují API za kompletní a za sebe dodám, že je to na mnoha místech vidět.:)

Několik důležitých informací:

  1. Projekt musí být stažen z mých stránek, jakékoli vystavování knihovny na jiném webu a serveru je zakázáno.
  2. Knihovnu jako celek v této verzi můžete používat dle libosti na komerčních i nekomerčních projektech. Zakázáno je samozřejmě vydávání knihovny za vlastní, její dekompilace a použití jen části knihovny.:) Jako autor knihovny nic negarantuji, nezodpovídám za případné přímé ani nepřímé škody vzniklé použitím knihovny a na opravu chyb knihovny není žádný nárok. Chyby lze reportovat na emailovou adresu PosterousAPI@renestein.net.
  3. Teprve dnes padlo rozhodnutí, že API v kódu musejí být komentovány v češtině. API zatím komentováno není a tento spot by vám měl pomoci se v knihovně zorientovat. Posterous API je součástí většího projektu. Posterous jsem si vymyslel a přidal do projektu sám a i když jsme s partnerem dohodnuti, že s Posterous knihovnou si mohu dělat, co chci, dokumentace musí být v češtině – stejně jako zbytek projektu. Pokusím se ale připravit i EN dokumentaci.
  4. Knihovna je zkompilována ve VS 2010 BETA 2 pro .Net Framework 3.5. Chci připravit i verze pro Compact .Net framework a Silverlight.

 

A nyní jž k samotnému API.

Branou k funkcím knihovny je třída PosterousApplication a její statická vlastnost Current.

Nejdříve se podíváme, jak pracovat s účtem Posterous. Metoda GetPosterousAccount vrací odkaz na objekt IPosterousAccount, který reprezentuje účet uživatele na službě Posterous.

using RStein.Posterous.API;
IPosterousAccount m_account = PosterousApplication.Current.GetPosterousAccount("posterousUserName", "posterousPassword");
  public interface IPosterousAccount : IExtensionInterface, IApplicationHolder
    {
        string Name { get; }
        IEnumerable<IPosterousSite> Sites { get; }
        void LoadSites();
        void LoadSitesAsync();
        event EventHandler<EventArgsValue<IEnumerable<IPosterousSite>>> SitesLoaded;
        IPosterousSite PrimarySite {get;}
        
    }

Nejzajímavější vlastností v rozhraní IPosterousAccount je vlastnost Sites, která obsahuje kolekci všech “blogů” uživatele. Kolekce Sites, stejně jako většina dalších vlastností a kolekcí i u jiných objektů, je naplněna daty až při prvním přístupu.

Jestliže chcete pracovat s výchozím blogem uživatele, můžete využít vlastnost IPosterousAccount.PrimarySite.

Rozhraní IPosterousSite

public interface IPosterousSite : IExtensionInterface
    {
        int Id { get; }
        string Name { get; }
        string Url { get; }
        bool IsPrivate { get; }
        bool IsPrimary{ get;}
        bool AreCommentsEnabled{ get; }
        IPosterousAccount PosterousAccount { get; set; }

        IEnumerable<IPosterousPost> Posts { get; }
        int TotalPosts { get; }
        int LoadedPosts { get; }
        void LoadAllPosts();
        void LoadAllPostsAsync();
        event EventHandler<EventArgsValue<IEnumerable<IPosterousPost>>> PostsLoaded;        

        IEnumerable<string> Tags { get; }
        bool IsTagsLoaded { get; }        
        void LoadTags();
        void LoadTagsAsync();
        event EventHandler<EventArgsValue<IEnumerable<string>>> TagsLoaded;
        
        IEnumerable<IPosterousPost> GetPostsByTag(string tag);
        void GetPostsByTagAsync(string tag);
        event EventHandler<EventArgsValue<IEnumerable<IPosterousPost>>> PostsByTagLoaded;

        IEnumerable<IPosterousPost> GetPostsInPage(int page, int recordsCount);
        void GetPostsInPageAsync(int page, int recordsCount);
        event EventHandler<EventArgsValue<IEnumerable<IPosterousPost>>> PostsInPageLoaded;

        IPosterousPost CreatePost(string title, string body, bool autopostAll);

        
    }

Každý blog (IPosterousSite) obsahuje blogspoty  - objekty podporující rozhraní IPosterousPost.

Assert.IsTrue(m_account.PrimarySite.Posts.Count() > 0); 
 public interface IPosterousPost : IEntityWithClientState
    {
        string Link { get; }
        string Title{ get; set; }        
        string Url { get; }
        int Id { get;}
        string Body {get; set;}                    
        DateTime PostDate { get; }
        int Views { get; }
        bool Private { get; }
        IAuthor Author { get; }
        bool AreCommentsEnabled { get; }                
        IPosterousComment CreateNewComment(string commentBody);
        void AppendMedium(string filePath);        
        IEnumerable<IPosterousComment> Comments { get; }
        IEnumerable<IPosterousMedium> Media { get; }
        IEnumerable<String> Tags{ get; }
        void AddTag(string tag);
        IPosterousSite Site { get; }
        void Refresh();
        
    }

Při přístupu k vlastnosti Posts jsou staženy všechny blogspoty v dávkách po 50 položkách. 50 položek najendou je interní omezení Posterous API. Jestliže nechcete nahrávat všechny blogspoty, můžete sami “stránkovat” a nahrávat blogspoty pomocí metody GetPostsIn Page.

//Nahraje z první stránky dva blogspoty
var posts = m_account.Sites.First().GetPostsInPage(1, 2);

Můžete také nahrát pouze blogspoty označené vybraným tagem. Seznam dostupných tagů zjistíte ve vlastnosti IPosterousSite.Tags. Dle mých zkušeností ale vrácení blogpostů nefunguje v Posterous API zcela správně a občas blogposty vráceny nejsou.

//Vrátí se blogposty označené tagem "Všechno a nic"
var posts = m_account.Sites.First().GetPostsByTag("Všechno a nic");

 

Kromě dalších zajímavých a samopopisných informací v každém blogspotu naleznete i kolekci komentářů k blogspotu (rozhraní IPosterousComment) a informaci o přiložených souborech (audio, foto, mp3… – rozhraní IPosterousMedium ) .

public interface IPosterousComment : IExtensionInterface 
    {
        int Id {get;}
        IAuthor Author {get;}
        DateTime CreateDate {get;}        
        string Body {get;}        
        IPosterousPost Post{get;}
        
        
    }
 public interface IPosterousMedium : IExtensionInterface
    {
        MediumType Type { get;}
        string Url { get;  }
        Stream Content { get; }
        int FileSize { get; }
        IDictionary<string, object> ExtendedInfo { get; }
        bool IsContentLoaded { get;}
        void LoadContent();
        void LoadContentAsync();   
        event EventHandler<EventArgs> ContentLoaded;
    }
 

U médií se vlastnost Content opět naplní až při přístupu a jakékoli další informace o médiích stažené z Posterous naleznete v kolekci ExtendedInfo – např. informace o náhledu obrázku.

Nové blogspoty je samozřejmě možné vytvářet i s médii.

//Nový post, první argument – titulek blogspotu, druhý argument tělo blogspotu, 
//třetí argument - pokud je true dojde automaticky k rozeslání postu na všechny další registrované služby -//(Twitter, FB...)
IPosterousPost newPost = m_account.PrimarySite.CreatePost("Obrázek HUDBA TeST",
                                                "Příliš žluťoučký kůň úpěl ďábelské ódy", true);

//Přidání obrázku
            newPost.AppendMedium(@"c:\Users\STEIN\Documents\Hudba\Once\AlbumArtSmall.jpg");
//Přidání mp3
            newPost.AppendMedium(@"c:\Users\STEIN\Documents\Hudba\Once\01_falling_slowly.mp3");
//Uložení postu na server
            newPost.SaveChanges();

Posterous bohužel nevrací po uložení automaticky veškeré informace o novém spotu (informace o médiích apod.), takže jsem zvolil mechanismus, kdy po volání SaveChanges je vždy ještě volána metoda Refresh, která přes další (Bit.Ly) API dotáhne podrobnosti, aby programátor nemusel na získání dodatečných údajů myslet a volat metodu Refresh sám.

Metodu Refresh ale samozřejmě sami volat můžete a získate tak vždy aktuální data ze serveru.

Uložený blogspot můžete editovat – ne všechny údaje lze nyní uložit na server, podívejte se na současný stav web API.

string updateText = "Updated " + DateTime.Now.ToString();
            post.AppendMedium(@"c:\Users\STEIN\Documents\Hudba\Dylan Bob - Time Out Of Mind\AlbumArt_{6DF0A444-4F68-489B-AFCF-A985B02166BB}_Large.jpg" );
            post.Body = updateText;
            post.Title = updateText;
            post.SaveChanges();

K uloženému blogspotu můžete přidávat nové komentáře.

var comment = post.CreateNewComment(updateText);            
            post.SaveChanges();
  

Posterous API dovoluje vytvořit zjednodušený nový blogpost, aniž byste museli mít na Posterous účet. K publikaci vám stačí předat jméno a heslo stávajícího twitter účtu.  Url nového blogspotu automaticky publikuje na twitter. Jestliže máte Posterous účet svázaný s twitter účtem, blogspot se uloží na vašem primárním blogu (Site). Toto API se hodí hlavně pro rychlou publikaci obrázků na twitter a  Posterous toto API považuje za alternativu ke službě TwitPic.

Nejprve opět přes vstupní objekt PosterousApplication získáte twitter účet (rozhraní ITwitterAccount) .

m_twitterAccount = PosterousApplication.Current.GetTwitterAccount("twitter_name", "twitter_password");
    public interface ITwitterAccount : IApplicationHolder
    {
        string UserName{ get; }
        ITwitterPost CreatePost(string title, string body, bool postToTwitter);        
    }
 

A takto vypadá rychlá publikace obrázku

//Nový post, první argument – titulek blogspotu, druhý argument - tělo blogspotu, třetí argument – pokud je true, automaticky dojde k publikaci url obrázku (blogspotu) na twitter.
ITwitterPost newPost = m_twitterAccount.CreatePost(null,
                                                               null, true); 

            newPost.AppendMedium(“c:\pic.jpg”);
            newPost.SaveChanges();
 

Rozhraní ITwitterPost.

public interface ITwitterPost : IEntityWithClientState
    {
        string Url { get; }
        string Title{ get; }
        string Body { get; }
        string MediaId { get; }
        void AppendMedium(string filePath);
        bool AutopostToTwitter { get; }
        ITwitterAccount TwitterAccount { get; }
        IEnumerable<String> MediaNames { get; }
        ISinglePostInfo GetPostInfo();
    }

Další API vám dovoluje získat informace o libovolném blogpostu, u kterého znáte Bit.ly  adresu – Bit.Ly adresa je každému blogpostu přiřazena při vytvoření  - vlastnost IPosterousPost.Url.

Tento blogpost nemusí pocházet z vašeho blogu (Site).

Opět přes objekt PosterousApplication získáte odkaz na IBitLyService.

 public interface IBitLyService : IApplicationHolder
    {
        ISinglePostInfo GetSinglePost(string bitLySuffix);
        void GetSinglePostAsync(string bitLySuffix);
        event EventHandler<EventArgsValue<ISinglePostInfo>>  SinglePostLoaded;
    }
 
Ukázka získání jednoduchého blogspotu z této služby.
Uri uri = new Uri(Url);
//Extenzní metoda GetBitLySuffix pro snadné získání suffixu
ISinglePostInfo post = bitLyService.GetSinglePost(uri.GetBitLySuffix())
 

Rozhraní ISinglePostInfo

 public interface ISinglePostInfo : IExtensionInterface
    {
        string Link { get; }
        string Title{get;}        
        string Url { get; }
        int Id { get;}
        string Body{get;}                    
        DateTime PostDate {get;}
        int Views { get; }
        bool Private { get; }
        IAuthor Author { get; }
        bool AreCommentsEnabled { get; }                        
        IEnumerable<IPosterousComment> Comments { get;}
        IEnumerable<IPosterousMedium> Media { get;}
        IEnumerable<String> Tags{ get;}        
    }
 

Pokročilejší nastavení, která by se vám mohla hodit.

Blogspoty mohou být označeny jménem aplikace, která je vytvořila, a odkazem na aplikaci.

PosterousApplication.Current.ApplicationName = "Moje cool aplikace";
PosterousApplication.Current.ApplicationUrl = http://renestein.net;

Chcete pracovat přímo s objekty HttpWebRequest a HttpWebResponse? Potřebujete doplnit autentizaci k proxy, nebo chcete změnit maximální dobu, po kterou bude trvat požadavek? S pomocí rozhraní IRawRequestResponsePublisher  je to jednoduché.

    public interface IRawRequestResponsePublisher : IExtensionInterface
    {
        event EventHandler<EventArgsValue<WebRequest>> WebRequestCreated;
        event EventHandler<EventArgsValue<WebResponse>> WebResponseCreated;
    }
 

Stačí zaregistrovat obslužné metody pro události a poté všechny objekty HttpWebRequest a HttpWebResponse, které interně knihovna používá,  můžete upravit dle libosti.

Ukázka změny vlastnosti Timeout.

IRawRequestResponsePublisher publisher =
                PosterousApplication.Current.GetInterface<IRawRequestResponsePublisher>();

            Debug.Assert(publisher != null);
            publisher.WebRequestCreated += ((_, e) => e.Value.Timeout = WEB_TIMEOUT);

C# Posterous API toho zvládne ještě více, ale myslím, že pro dnešek už bylo kódu dost. Užijte si to. :-)



Friday, January 15, 2010 5:49:51 PM (Central Europe Standard Time, UTC+01:00)       
Comments [1]  .NET Framework | C# Posterous API | Compact .Net Framework | Silverlight


 Wednesday, November 18, 2009
Vynucení si překreslení celé obrazovky v Compact .Net Frameworku

Při snaze kreslit grafické objekty na celou obrazovku PDA, a ne pouze na vlastní formulář, se můžete velmi často setkat s dotazem, jak zajistím, že předchozí nakreslené dílko, rozprostřené většinou přes vícero formulářů a mimo naši přímou kontrolu, smažu před vykreslením dalšího dílka. Následující příklad je reakcí na takový dotaz. Za “celou obrazovkou” budeme v článku považovat grafický kontext vrácený voláním metody GetDC s argumentem NULL. Po celé obrazovce PDA je postupně vykreslován kruh (s velkou fantazií míč) pohybující se z levé strany displeje na pravou. Před vykreslením “míče” na další pozici musí být míč vykreslený v předcházejícím kroku smazán. Následující kód je ukázkou použití “brutální síly”, protože žádné slečinkovské, sexy ani elegantní konstrukce s překreslováním pouze části obrazovky k výsledku nevedly. Hlavní trik, ke kterému jsem dospěl po sundání bílých vývojářských rukaviček, spočívá v rekurzivním vynucení si překreslení všech oken v metodě DoWork s využitím přímého volání mnoha nativních API funkcí, které mají paradoxně v aplikacích cílených na Microsoftem macešsky spravovaný a rozvíjený Compact .Net Framework (i ve verzi. 3.5) stále privilegovanou pozici.

 using System;
 using System.ComponentModel;
 using System.Drawing;
 using System.Threading;
 using System.Windows.Forms;

namespace RedrawScreenTest
{
    

    partial class Form1 : Form
    {

        #region constants
        
        public const int POSITION_INCREMENT = 10;
        public const int MAX_POSITION = 200;
        public const int MAX_THREAD_WAIT = 5000;
        public const int CIRCLE_RADIUS = 30;

        #endregion constants
        
        #region delegates
        public delegate void InvokeDelegate();
        #endregion delegates

        #region Properties
            public IntPtr WindowsHDC { get; set; }        
            public Thread Worker { get; set; }        
            public bool ShouldWork { get; set; }        
            public bool IsWorking { get; set; }
        #endregion Properties


         #region Constructors
            public Form1()
        {
            InitializeComponent();  
            WindowsHDC = IntPtr.Zero;
            ShouldWork = false;
            IsWorking = false;
        }
       #endregion Constructors

            #region methods
            public void DoWork()
        {
            IsWorking = true;
            int x = 0;
            int y = 0;
            

            bool firstDraw = true;


            while (ShouldWork)
            {
                WindowsHDC = ApiWrapper.GetDC(IntPtr.Zero);

                if (WindowsHDC == IntPtr.Zero)
                {
                    break;
                }

                


                if (!firstDraw)
                {

                    Invoke((InvokeDelegate) (() =>
                                                 {

                                                     IntPtr hwnd = ApiWrapper.GetForegroundWindow();

                                                     hwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_HWNDFIRST);


                                                     while (hwnd != IntPtr.Zero)
                                                     {

                                                         ApiWrapper.RedrawWindow(hwnd, IntPtr.Zero, IntPtr.Zero,
                                                                      ApiWrapper.RDW_ERASE | ApiWrapper.RDW_INVALIDATE | ApiWrapper.RDW_ALLCHILDREN);

                                                         
                                                         EnumChild(hwnd, 0);

                                                         hwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_HWNDNEXT);

                                                     }
                                                 }));
                }
                else
                {
                      
                       firstDraw = false;       

                }



                using (Graphics g = Graphics.FromHdc(WindowsHDC))
                using (Brush b = new SolidBrush(Color.Yellow))
                {
                    g.FillEllipse(b, new Rectangle(x, y, CIRCLE_RADIUS, CIRCLE_RADIUS));                    
                    
                }
                    
                
                
                    ApiWrapper.ReleaseDC(IntPtr.Zero, WindowsHDC);
                    

                    Thread.Sleep(1000);

                    x += POSITION_INCREMENT;
                    y += POSITION_INCREMENT;

                    if (x > MAX_POSITION || y > MAX_POSITION)
                    {
                        x = 0;
                        y = 0;
                    }
                }

                IsWorking = false;
            }
        
        private void EnumChild(IntPtr hwnd, int level)
        {

            if ((hwnd == IntPtr.Zero))
            {
                return;
            }

            IntPtr childHwnd = ApiWrapper.GetWindow(hwnd, (int)ApiWrapper.GWConstants.GW_CHILD);            

            while (childHwnd != IntPtr.Zero)
            {
                EnumChild(childHwnd, level + 1);
                ApiWrapper.RedrawWindow(childHwnd, IntPtr.Zero, IntPtr.Zero,
                          ApiWrapper.RDW_ERASE | ApiWrapper.RDW_INVALIDATE | ApiWrapper.RDW_ALLCHILDREN);

                childHwnd = ApiWrapper.GetWindow(childHwnd, (int)ApiWrapper.GWConstants.GW_HWNDNEXT);

            }
            

            

        }
        private void Form1_Load(object sender, EventArgs e)
        {
            
        }

        
        private void Form1_Closing(object sender, CancelEventArgs e)
        {
            buttonStop_Click(this, new EventArgs());


            if (WindowsHDC != IntPtr.Zero)
            {
                ApiWrapper.ReleaseDC(IntPtr.Zero, WindowsHDC);
            }
                
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            if (IsWorking)
            {
                return;
            }
            
            ShouldWork = true;
            Worker = new Thread(DoWork);
            Worker.Start();
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            if (!IsWorking)
            {
                return;
            }
            
            ShouldWork = false;
            Worker.Join(MAX_THREAD_WAIT);
        }

           

        private void Form1_Paint(object sender, PaintEventArgs e)
        {

        }

        #endregion methods
    }   
  }  

Zde jsou potřebné deklarace API funkcí, konstant a struktur


using System;
using System.Drawing;
using System.Runtime.InteropServices;

namespace RedrawScreenTest
{
    class ApiWrapper
    {
        [DllImport("coredll.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate,
                IntPtr hrgnUpdate, uint flags);

        public const int RDW_INVALIDATE = 0x0001;
        const int RDW_INTERNALPAINT = 0x0002;
        public const int RDW_ERASE = 0x0004;

        const int RDW_VALIDATE = 0x0008;
        const int RDW_NOINTERNALPAINT = 0x0010;
        const int RDW_NOERASE = 0x0020;

        const int RDW_NOCHILDREN = 0x0040;
        public const int RDW_ALLCHILDREN = 0x0080;

        const int RDW_UPDATENOW = 0x0100;
        const int RDW_ERASENOW = 0x0200;

        const int RDW_FRAME = 0x0400;
        const int RDW_NOFRAME = 0x0800;



        [DllImport("coredll.dll", SetLastError = true)]
        public static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

        public enum GWConstants
        {
            GW_HWNDFIRST = 0,
            GW_HWNDLAST = 1,
            GW_HWNDNEXT = 2,
            GW_HWNDPREV = 3,
            GW_OWNER = 4,
            GW_CHILD = 5,
            GW_ENABLEDPOPUP = 6
        }



        [DllImport("coredll.dll")]
        public static extern IntPtr GetForegroundWindow();

        [DllImport("coredll.dll", EntryPoint = "GetWindowDC")]
        public static extern IntPtr GetDC(IntPtr ptr);
        
        [DllImport("coredll.dll", EntryPoint = "ReleaseDC")]
        public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDc);
        
        [DllImport("coredll.dll", EntryPoint = "GetDesktopWindow")]
        public static extern IntPtr GetDesktopWindow();
        
        [DllImport("coredll.dll", EntryPoint = "UpdateWindow")]
        public static extern bool UpdateWindow(IntPtr hWnd);
        
        [DllImport("coredll.dll", EntryPoint = "UpdateWindow")]
        public static extern int SendMessage(IntPtr hWnd, uint msg, int wparam, int lparam);
        
        [DllImport("coredll.dll")]
        public extern static void InvalidateRect(IntPtr handle, Rectangle dummy, bool erase);

        [DllImport("coredll.dll", EntryPoint = "FindWindowW", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);



    }
}


Wednesday, November 18, 2009 3:02:51 PM (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Compact .Net Framework


 Sunday, September 20, 2009
Pozvánka na podzimní kurzy (OOP, UML, základní a pokročilé návrhové vzory)

Aktualizace  10. 11. 2009-  I veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 2 je zcela obsazen včetně náhradníků. Další kurzy se budou konat na jaře 2009. Jestliže máte předběžný zájem a chcete si rezervovat místo, pište prosím na adresu petra@renestein.net.

Aktualizace 15.10.2009  - veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1 je zcela obsazen včetně náhradníků. Je možné se již hlásit pouze na kurz Pokročilé návrhové vzory a objektové principy 2.

Rád bych Vás pozval na podzimní kurzy OOP a UML a představil oficiálně InHouse kurz, který postupně vykrystalizoval z požadavků zákazníků (OOP 0 - Objektové programování a UML prakticky - rychlý úvod do světa (nejen) objektového programování).

Osnova InHouse kurzu OOP 0 – Objektové programování a UML prakticky – rychlý úvod do světa (nejen) objektového programování:

Školení má dvě varianty -  pro vývojáře i u konstrukcí a prvků jazyka UML, které jsou považovány za analytické, se dělají časté odbočky do kódu, aby vývojáři pochopili, že UML ani principy OOP nejsou nějaké nesmyslné abstrakce, ale užitečné konstrukce, které sami v programovacích jazycích používají denně.

U varianty pro „čisté“ analytiky jsou digrese do kódu minimalizovány, i když v některých místech stále zdůrazňuji, jaké znalosti z oblasti vývoje aplikací musí analytik mít, aby byl pro projekt užitečný a nevytvářel jen dokumentaci pro dokumentaci, kterou vývojáři nevyužijí a (mnohdy oprávněně) považují za nesmyslnou, drahou a projektu nic nepřinášející.

V kurzu se naučíte modelovat jednoduché i složité aplikace s využitím jazyka UML tak, aby následné kódování nebylo výletem do neznáma s nejistými výsledky, ale dobře čitelnou cestou bez temných a záludných míst vedoucích k selhání projektu.

Kurz je vhodný zvláště pro ty, kteří již nejsou spokojeni s vývojem projektů naivním "hurá" způsobem, kdy bez ohledu na složitost systému nevzniká žádný návrh a ihned se přistupuje ke kódování se všemi špatnými důsledky jako jsou podcenění technické a časové náročnosti implementace nebo vytváření drahých a nespravovatelných systémů.

Kurz je určen pro vývojáře, systémové designery, analytiky a projektové manažery, kteří se chtějí se seznámit se základními principy objektového programování a s modelováním v jazyce UML.

· Požadavky na systém a modelování pomocí případů užití (+ příklady).

· Zrychlená funkční specifikace bez zbytečných formalit – příklady.

· Diagram tříd v UML - vztahy mezi elementy diagramu (asociace. agregace, generalizace, závislost, realizace) – vše vykládáno na konkrétních příkladech z praxe + ukázky nejčastějších chyb, se kterými jsem se setkal. Třída, základní principy OOP, operace, atributy, viditelnost členů třídy. Nenásilný přechod k jednoduchým návrhovým vzorům.

· Příklady složitých diagramů tříd.

· Objektový diagram + příklady.

· Sekvenční diagramy a diagramy interakce.

· Vysvětlení stavových diagramů + výhody aplikací řízených přesně definovanými stavovými automaty.

· Diagram aktivit - modelování složitých business procesů v organizaci.

· Výhody a nevýhody UML - vyzdvižení nejvíce používaných postupů, odhození nepotřebné veteše z jazyka UML.

 

Pokud máte o kurz zájem nebo potřebujete další informace, napište prosím na adresu petra@renestein.net.


Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1

Datum konání kurzu:  2. 11. – 4. 11. 2009

Místo konání: Hotel VILLA Praha  Okrajní 1, 100 00, Praha 10

U hotelu VILLA je  možné parkovat, po celý den máme k dispozici wifi připojení.

Na kurzu jsou samozřejmě po celý den teplé a studené nápoje a v ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurz


Veřejný kurz Pokročilé návrhové vzory a objektové principy 2

Datum konání kurzu:  23. 11. – 25. 11. 2009

Místo konání: Hotel VILLA Praha  Okrajní 1, 100 00, Praha 10

U hotelu VILLA je  možné parkovat, po celý den máme k dispozici wifi připojení.

Na kurzu jsou samozřejmě po celý den teplé a studené nápoje a v ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurzy



Sunday, September 20, 2009 5:25:11 PM (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory


 Monday, July 27, 2009
Odchytnutí zprávy WM_KEYDOWN v dialogu – Windows Mobile

V jednom předchozím článku jsem slíbil, že na blog dám i kód, který umožní ve Windows dialogu zachytit všechny stisknuté klávesy. Jak možná víte, dialog ve Windows je běžné okno (Window) s třídou (class) WC_DIALOG. K dialogu je přiřazena speciální funkce WNDPROC, která zajišťuje výchozí zpracování zpráv zaslaných formuláři (např. přechod mezi prvky dialogu pomocí klávesy TAB) a volá vývojářem aplikace určenou obslužnou funkci dialogu (DLGPROC). Jedním z nepříjemných důsledků tohoto modelu chování pro dialogy je, že nejsme schopni v DLGPROC odchytit a zpracovat zprávu o stisknutí tlačítka na klávesnici (WM_KEYDOWN).

Nechceme-li reimplementovat všechny vychytávky dialogů v našem vlastním “okně” (Window) a současně chceme i v dialogu odchytit zprávu WM_KEYDOWN, musíme výchozí WNDPROC obslužnou funkci při vytváření dialogu nahradit naší vlastní “proxy” WNDPROC funkcí.


BOOL CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_INITDIALOG)
    {
        ChangeDialogWndProc(hWnd);
    }
    
    return true;
}

void ChangeDialogWndProc(HWND hwnd)
{
    g_oldDlgdProc = (WNDPROC)GetWindowLong(hwnd, GWL_WNDPROC);
    SetWindowLong(hwnd, GWL_WNDPROC, (LONG)&DlgWindowsProc);
    
}

V naší  obslužné proceduře dialogu DlgProc při inicializaci dialogu (zpráva WM_INITDIALOG) voláme funkci ChangeDialogWndProc, která zaregistruje naší “proxy” WINDPROC funkci pomocí API SetWindowLong. Ještě předtím si uložíme do proměnné g_oldDlgdProc pointer na předchozí WNDPROC funkci, která je návratovou hodnotou API funkce GetWindowLong, když jí  ve druhém argumentu předáme konstantu GWL_WNDPROC.

V naší “proxy” funkci WNDPROC odchytneme všechny potřebné zprávy a když chceme zachovat výchozí chování dialogu, předáme zprávu ke zpracování  v předchozím kroku uložené výchozí Windows proceduře pro dialogy.

RESULT CALLBACK DlgWindowsProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_KEYDOWN)
    {
        //Do something with key
        int key = (int) wParam;
    }
    
    return g_oldDlgdProc(hWnd, message, wParam, lParam);
    
}

 

Následuje jednoduchý příklad  založený na standardní šabloně WM projektu.

// HookDialog.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "HookDialog.h"


#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE            g_hInst;            // current instance
HWND                g_hWndMenuBar;        // menu bar handle
WNDPROC g_oldDlgdProc;

// Forward declarations of functions included in this code module:
ATOM            MyRegisterClass(HINSTANCE, LPTSTR);
BOOL            InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
void ChangeDialogWndProc(HWND hwnd);
BOOL CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK DlgWindowsProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
    MSG msg;

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow)) 
    {
        return FALSE;
    }


    DialogBox(g_hInst, MAKEINTRESOURCE(IDD_POCKETPC_PORTRAIT), NULL, &DlgProc);
    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0)) 
    {
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
    WNDCLASS wc;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_HOOKDIALOG));
    wc.hCursor       = 0;
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName  = 0;
    wc.lpszClassName = szWindowClass;

    return RegisterClass(&wc);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND hWnd;
    TCHAR szTitle[MAX_LOADSTRING];        // title bar text
    TCHAR szWindowClass[MAX_LOADSTRING];    // main window class name

    g_hInst = hInstance; // Store instance handle in our global variable

    // SHInitExtraControls should be called once during your application's initialization to initialize any
    // of the device specific controls such as CAPEDIT and SIPPREF.
    SHInitExtraControls();

    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 
    LoadString(hInstance, IDC_HOOKDIALOG, szWindowClass, MAX_LOADSTRING);

    //If it is already running, then focus on the window, and exit
    hWnd = FindWindow(szWindowClass, szTitle);    
    if (hWnd) 
    {
        // set focus to foremost child window
        // The "| 0x00000001" is used to bring any owned windows to the foreground and
        // activate them.
        SetForegroundWindow((HWND)((ULONG) hWnd | 0x00000001));
        return 0;
    } 

    if (!MyRegisterClass(hInstance, szWindowClass))
    {
        return FALSE;
    }

    hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

    if (!hWnd)
    {
        return FALSE;
    }

    
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);


    return TRUE;
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND    - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY    - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;

    
    switch (message) 
    {
        case WM_COMMAND:
            wmId    = LOWORD(wParam); 
            wmEvent = HIWORD(wParam); 
            // Parse the menu selections:
            switch (wmId)
            {
                case IDM_OK:
                    DestroyWindow(hWnd);
                    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
            }
            break;
        case WM_CREATE:
            SHMENUBARINFO mbi;

            memset(&mbi, 0, sizeof(SHMENUBARINFO));
            mbi.cbSize     = sizeof(SHMENUBARINFO);
            mbi.hwndParent = hWnd;
            mbi.nToolBarId = IDR_MENU;
            mbi.hInstRes   = g_hInst;

            if (!SHCreateMenuBar(&mbi)) 
            {
                g_hWndMenuBar = NULL;
            }
            else
            {
                g_hWndMenuBar = mbi.hwndMB;
            }

            break;
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            
            // TODO: Add any drawing code here...
            
            EndPaint(hWnd, &ps);
            break;
        case WM_DESTROY:
            CommandBar_Destroy(g_hWndMenuBar);
            PostQuitMessage(0);
            break;


        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}


BOOL CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_INITDIALOG)
    {
        ChangeDialogWndProc(hWnd);
    }
    
    return true;
}

LRESULT CALLBACK DlgWindowsProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_KEYDOWN)
    {
        //Do something with key
        int key = (int) wParam;
    }
    
    return g_oldDlgdProc(hWnd, message, wParam, lParam);
    
}

void ChangeDialogWndProc(HWND hwnd)
{
    g_oldDlgdProc = (WNDPROC)GetWindowLong(hwnd, GWL_WNDPROC);
    SetWindowLong(hwnd, GWL_WNDPROC, (LONG)&DlgWindowsProc);
    
}


Monday, July 27, 2009 11:29:07 AM (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Compact .Net Framework | Nativní kód


 Tuesday, June 09, 2009
Chyba při používání prvku Popup v Silverlightu 2.0

Tento spot se objevil již před nějakou dobou jako komentář na Zdrojáku, ale protože se s podobným problémem setkal i Michal Kočí na Twitteru, dostal jsem pár emailů s popisem chyby v Silverlightu a znovu jsme narazili na podobné chyby při portaci firemního frameworku, dávám původní komentář i sem na na blog, abych měl kam trvale odkazovat další zoufalce. :)

Jestliže máte vlastni User Control, ve kterém je Popup a tento Popup neobsahuje ListBox (a možná další prvky), je možné Popup zobrazit a používat, aniž by byl přidán do kolekce Children rodičovské “stránky” (třída typicky nazvaná Page  – potomek UserControl) v Silverlightu. Takto definovaný POPUP funguje bez problémů.

<pexeso:popupbase x:class="RStein.Pexeso.SaveFile" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pexeso="clr-namespace:RStein.Pexeso">

    <grid x:name="LayoutRoot" background="Black">

        <popup name="filesPopup">

            <popup.child>

                <stackpanel orientation="Vertical" background="Red">

                    <textblock text="Název souboru s uloženou hrou" fontsize="15" margin="5,5,5,0" horizontalalignment="Left" foreground="White" textdecorations="Underline"></textblock>

                    <stackpanel orientation="Horizontal">

                        <textbox name="txtFile" margin="5" minwidth="200"></textbox>

                        <textblock foreground="Yellow" visibility="Collapsed" name="txtError" text="Musíte zadat platný název souboru!" horizontalalignment="Left" verticalalignment="Center" fontweight="Bold" fontsize="10"></textblock>

                    </stackpanel>

                    <stackpanel orientation="Horizontal" margin="5">

                        <button style="{StaticResource DialogButton}" content="Uložit" name="btnSelect" click="btnSelect_Click"></button>

                        <button style="{StaticResource DialogButton}" content="Zpět" name="btnBack" click="btnCancel_Click"></button>

                    </stackpanel>

                </stackpanel>

            </popup.child>

        </popup>

    </grid>

</pexeso:popupbase>

 

Jestliže ale Popup obsahuje Listbox (a pravděpodobně i jiné prvky), Popup se zobrazí, ale při vybrání libovolné položky v ListBoxu celý Silverlight plugin spadne do obsluhy události UnhandledException a napíše jen něco o interní fatální chybě. Mimochodem, Bety a RC Silverlightu tímto problémem podle mě netrpěly.

Tento popup způsobí pád Silverlightu, jestliže popup není před svým zobrazením přidán do kolekce Children.

<pexeso:popupbase x:class="RStein.Pexeso.SelectFile" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pexeso="clr-namespace:RStein.Pexeso">

    <grid x:name="LayoutRoot" background="Black">

        <popup name="filesPopup">

            <popup.child>

                <stackpanel orientation="Vertical" background="Red">

                    <textblock text="Vyberte uloženou hru" fontsize="15" margin="5,5,5,0" horizontalalignment="Left" foreground="White" textdecorations="Underline"></textblock>

                    <textblock foreground="Yellow" visibility="Collapsed" fontsize="10" name="txtError"></textblock>

                    <border cornerradius="20" background="White">

                        <listbox name="lstFiles" background="Orange" height="200">

                            <listbox.itemtemplate>

                                <datatemplate>

                                    <textblock text="{Binding Mode=OneWay}" foreground="White"></textblock>

                                </datatemplate>

                            </listbox.itemtemplate>

                        </listbox>

                    </border>

                    <stackpanel orientation="Horizontal">

                        <button style="{StaticResource DialogButton}" content="Vybrat soubor" name="btnSelect" click="btnSelect_Click"></button>

                        <button style="{StaticResource DialogButton}" content="Zpět" name="btnBack" click="btnCancel_Click"></button>

                    </stackpanel>

                </stackpanel>

            </popup.child>

        </popup>

    </grid>

</pexeso:popupbase>

 

Před zobrazením Popupu tedy musíme vždy přidat Popup do kolekce Children a po uzavřeni Popupu jej případně odebrat.


 

 private void btnLoad_Click(object sender, RoutedEventArgs e)

        {

            SelectFile file = new SelectFile();

            var files = FileAccessComponent.Instance.GetRootFiles();

            if (files.Length == 1)

            {

                return;

            }

 

            file.FileListBox.ItemsSource = files;           

 

            LayoutRoot.Children.Add(file); //Přidat do kolekce

 

            file.DialogClosed += file_DialogClosed; //Vlastní událost pro všechny mé dialogy

            showPopup(file.FilesPopup);

            file.FileListBox.Focus();

        }

 

        void file_DialogClosed(object sender, EventArgs e)

        {

            SelectFile sfDialog = sender as SelectFile;

 

            try

            {

                if (sfDialog.LastResult == DialogResult.OK && sfDialog.FileListBox.SelectedItem != null)

                {

                    m_currentGame = PexesoGame.Load(sfDialog.FileListBox.SelectedItem.ToString());

                    removeButtons();

                    rebindGameData();

                }

            }

            catch (Exception e1)

            {

                Console.WriteLine(e1);

            }

            finally

            {

                LayoutRoot.Children.Remove(sfDialog); //Odebrat z kolekce

                sfDialog.DialogClosed -= saveFileDialog_DialogClosed;

                hidePopup(sfDialog.FilesPopup);

            }

 

        }



Tuesday, June 09, 2009 9:51:20 AM (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Silverlight


 Friday, April 24, 2009
Drobná poznámka ke kontravariancí delegátů v C#

Předpokládám, že se stejně jako já těšíte na lepší podporu kovariance a kontravariance u rozhraní a delegátů v připravované verzi  C# 4.0. Už dnes se ale dá s existující podporou kovariance a kontravariance u delegátů pěkně kouzlit – pro ty s exaktnějším přístupem ke kódu a vytříbenou terminologií se slovo “kouzlit” v knihách zásadně překládá jako “psát elegantnější kód”. Opakovat základy kovariance a kontravariance u delegátů zde nebudu a všechny ty, kteří sem zabloudili při svém ahashverovském “googlování” nějakého problému volně souvisejícího s probíraným tématem, odkážu na článek v MSDN. :-)

Kovarianci i kontravarianci delegátů používám rád, ale dnes se mi podařilo narazit na potíž, o které si nejsem jistý, že je všeobecně známa. Alespoň já jsem se po chvíli údivu a narůstajícího rozčilení nad tím, že můj dokonalý kód :-) nechce vybíravý kompilátor přijmout a neustále protestuje, musel zbaběle uchýlit ke specifikaci C# 3.0.

 

Takže zde je popis “problému”.

Tento kód asi nikoho nepřekvapí

 public delegate void MyAction<T>(T t);

    
    public class Base
    {
        
    }
    public class Derived : Base
    {
        
    }
    class Program
    {
        static void Main(string[] args)
        {

            MyAction<Derived> del = Test;
            
        }


        public static void Test(Base p)
        {
            Console.WriteLine(p);
            
    }
}

Máme generického delegáta MyAction, jehož instanci s názvem del vytvoříme v metodě Main. Za generický parametr T dosadíme generický argument typu “Derived” , přičemž delegát ukazuje na metodu Test, která přijímá argument Typu Base. Kontravariance zajistí, že tento kód bez problémů projde.

Zkusme udělat mírnou úpravu. Místo metody Test přiřadíme do delegáta del lambda výraz, u kterého explicitně určíme typ argumentu.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ContravarianceTest
{
    public delegate void MyAction<T>(T t);

    
    public class Base
    {
        
    }
    public class Derived : Base
    {
        
    }
    class Program
    {
        static void Main(string[] args)
        {

            MyAction<Derived> a = (Base b) => Console.WriteLine(b) ;
                          
            
        }

    }

}

Kompilátor se tentokrát razantně ohradí proti podobné manipulaci.

Cannot convert lambda expression to delegate type 'ContravarianceTest.MyAction<ContravarianceTest.Derived>' because the parameter types do not match the delegate parameter types    C:\Documents and Settings\STEIN\Dokumenty\CovarianceTest\Program.cs   

A v další chybě své výhrady upřesní.

Parameter '1' is declared as type 'ContravarianceTest.Base' but should be 'ContravarianceTest.Derived'    C:\Documents and Settings\STEIN\Dokumenty\CovarianceTest\Program.cs   

Shrneme-li to, je zřejmé, že při použití lambda výrazu kompilátor na nějakou kontravarianci argumentů zapomene a vyžaduje, aby typ argumentu v lambda výrazu byl identický s typem argumentu u delegáta. Ještě podotknu, že stejné chování s projeví i u negenerického delegáta - public delegate void MyAction(Derived  d);.

Použijeme-li anonymní metodu, kompilátor stále protestuje jako u lambda výrazů.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ContravarianceTest
{
    public delegate void MyAction<T>(T t);

    
    public class Base
    {
        
    }
    public class Derived : Base
    {
        
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyAction<Derived> a = delegate(Base b)
                                      {
                                          Console.WriteLine(b);
                                      };
              

            
        }


    }

}

Cannot convert anonymous method to delegate type 'ContravarianceTest.MyAction<ContravarianceTest.Derived>' because the parameter types do not match the delegate parameter types    C:\Documents and Settings\STEIN\Dokumenty\CovarianceTest\Program.cs    27    35

Parameter '1' is declared as type 'ContravarianceTest.Base' but should be 'ContravarianceTest.Derived'    C:\Documents and Settings\STEIN\Dokumenty\CovarianceTest\Program.cs    27    44

U anonymních metod a lambda výrazů tedy kontravarianci nehledejme. Ve specifikacci C# 3.0 (C# 3.0 specification – sekce 7.14.1) nalezneme popis omezení pro anonymní metody a lambda výrazy.

“If an anonymous function has an explicit-anonymous-function-signature, then the set of compatible delegate types and expression tree types is restricted to those that have the same parameter types and modifiers in the same order. In contrast to method group conversions (§6.6), contra-variance of anonymous function parameter types is not supported. [zvýraznil R.S.] If an anonymous function does not have an anonymous-function-signature, then the set of compatible delegate types and expression tree types is restricted to those that have no out parameters.”

Většina programátorů v C# asi nikdy na podobné omezení nenarazí, a když už ano, vezmou jako fakt, “že to asi z nějakého důvodu nejde”. Mně přijde podobné chování neintutivní a vsadil bych se, že vetšina vývojářů, ať už vědomě či podvědomě, na C# oceňuje, že jde o jazyk, ve kterém na ně nečíhá mnoho záludností nebo nepříjemných překvapení. S přidáváním dalších a dalších rysů do jazyka vzrůstá pravděpodobnost, že některá nová vlastnost začne ovlivňovat způsob použití starší vlastnosti v jazyce a také vzrůstá počet rozdílů mezi konstrukcemi, které na první pohled vypadají stejně, nebo alespoň od nich poučenější vývojář, ačkoli si je vědom některých rozdílů například mezi delegátem ukazujícím na tradiční funkci, anonymní metodu, lamda výraz a složenou lambdu (lambda statement), očekává podobné chování.

Celou poznámku bychom mohli uzavřít dotazem: “Kolik nových vlastností programovací jazyk snese bez šrámů a posléze pořádných zářezů  na pověsti “jednoduchého” jazyka? Tipnul bych si, že empiricky si to budeme moci ověřit, až se začnou na fórech množit zoufalí vývojáři naříkající nad složitostí jazyka, jako se to děje dnes, když nějaká lama poté, co zbastlila při svých hrátkách “skorofunkčnípůlaplikaci” ve VB, Javě či C#, je nucena programovat v C++”? :-) Jde samozřejmě o hyperbolu, vždyť vím, že budoucnost na krásném IT úhoru je otevřena právě pro všechny ty pilné dělníky nové éry, kteří ochotně vygooglují různé nesouvislé fragmenty kódu, jež považují za společný komunitní majetek k instantnímu užití, dále zkombinují několik z nebe spadlých frameworků dohromady a jsou patřičně hrdi na to, že po dlouhé  praxi znají alespoň přibližný význam poloviny klíčových slov v C# či Javě.  Tedy pro ty, kterým třeba kontravariance v programovacím jazyce nikdy chybět nebude. :-)



Friday, April 24, 2009 11:57:02 AM (Central Europe Standard Time, UTC+01:00)       
Comments [6]  .NET Framework | Compact .Net Framework


 Tuesday, April 14, 2009
Windows Mobile formulář přes celý displej - v nativním kódu

Na MSDN fórech jsem si všiml, že se vícekrát objevil dotaz, jak v nativním kódu vytvořit okno přes celou obrazovku, které se bude chovat jako formulář v Compact .Net Framework aplikaci při nastavení vlastnosti WindowState = Maximized.

API SHFullScreen sice přepne okno do celoobrazovkového režimu, ale při zobrazení SIPu se opět objeví taskbar. Při skrytí SIPu se okno vrátí do celoobrazovkového režimu. První, co mě napadlo, je skrýt samotný taskbar. Idea dobrá, mohli jsme mít jednoduché řešení,  ale autoři Windows Mobile jako již tradičně řekli ne.

Po několika pokusech jsem zjistil, že jediné použitelné řešení představuje změna pozice a velikosti formuláře vždy, když odchytím zprávu WINDOWPOSCHANGED. To celé je korunováno opakovaným voláním SHFullScreen. Na řešení v Compact .Net Frameworku jsem se nedíval, abych si nekazil radost z vyřešeného úkolu, takže netuším, zda autoři CNF používají ještě nějaký další trik.:-)

Níže naleznete příklad, který je založen na výchozí šabloně Smart Device Windows API projektu. Zajímavé části jsou zvýrazněny tučně. Tento postup lze samozřejmě jednoduše použít ve WTL nebo MFC.

Tady se ještě zeptám:

1) Jsou alespoń pro někoho z Vás tyto tipy/FAQ zajímavé? Já sám miluji přecházení mezi nativním a “managed” kódem, ale asi nemá smysl, abych tyto tipy psal na blog, jestliže o nativní kód (již) nikdo nestojí.Potom by stačílo, abych je nechal utopeny ve fóru o mobilních zařízeních, kde poslouží podobným individuím jako jsem já. V zásobě mám například často kladený dotaz, jak ve Windows Mobile dialogu zachytit WM_KEY zprávy. :-)  I když sám si stále programování v (Compact) .Net frameworku bez dobré znalosti nativního kódu nedovedu představit - což je v roce 2009 možná tristní a nečekaná zpráva.:-)

2) A obecnější dotaz – zajímají někoho z vás tipy pro Windows Mobile/Compact .Net Framework? Pro mě, jak asi tušíte, je programování pro WIN Mobile zařízení potěšení, a proto se podobné tipy objevují i na blogu, který píšu hlavně pro zábavu. I když většinu času jsem nyní strávil vývojem v Silverlightu, WPF, WCF a léčením  roztomilých neštovic na zpočátku krásné tváři Linq2SQL, což znamená, že se na blogu se objeví i další témata, která se budou točit kolem návrhu různých typů aplikací a jako bonus odhalíme nejčastější průšvihy spojené s anemickými modely (i model-view-viewmodely :-))aplikací.

// FullScreen.cpp : Defines the entry point for the application. 
// 
 
#include "stdafx.h"
#include "FullScreen.h"  
 
 
#define MAX_LOADSTRING 100  
 
// Global Variables: 
HINSTANCE           g_hInst;            // current instance 
HWND                g_hWndMenuBar;      // menu bar handle 
RECT usedRect;  
// Forward declarations of functions included in this code module: 
ATOM            MyRegisterClass(HINSTANCE, LPTSTR);  
BOOL            InitInstance(HINSTANCE, int);  
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);  
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);  
void MakeFullScreen();  
 
void MakeFullScreen(HWND hWnd)  
{  
      
    SetRect(&usedRect, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));  
      
    LONG windowWidth = usedRect.right - usedRect.left;  
    LONG windowHeight = usedRect.bottom - usedRect.top;  
 
    MoveWindow(hWnd,   
               usedRect.left,   
               usedRect.top,  
               windowWidth,  
               windowHeight,  
               FALSE);  
 
    //SIPINFO info;  
    //info.cbSize = sizeof(info);  
    //ZeroMemory(&info, sizeof(info));  
    //info.rcVisibleDesktop = usedRect;  
    //  
    //if (!::SipSetInfo(&info))  
    //{  
    //  int error = GetLastError();  
    //  return FALSE;  
    //}  
 
    SHFullScreen(hWnd, SHFS_HIDETASKBAR | SHFS_HIDESTARTICON);  
      
      
      
}
 
int WINAPI WinMain(HINSTANCE hInstance,  
                   HINSTANCE hPrevInstance,  
                   LPTSTR    lpCmdLine,  
                   int       nCmdShow)  
{  
    MSG msg;  
 
    // Perform application initialization: 
    if (!InitInstance(hInstance, nCmdShow))   
    {  
        return FALSE;  
    }  
 
    HACCEL hAccelTable;  
    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FULLSCREEN));  
 
    // Main message loop: 
    while (GetMessage(&msg, NULL, 0, 0))   
    {  
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))   
        {  
            TranslateMessage(&msg);  
            DispatchMessage(&msg);  
        }  
    }  
 
    return (int) msg.wParam;  
}  
 
// 
//  FUNCTION: MyRegisterClass() 
// 
//  PURPOSE: Registers the window class. 
// 
//  COMMENTS: 
// 
ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)  
{  
    WNDCLASS wc;  
 
    wc.style         = CS_HREDRAW | CS_VREDRAW;  
    wc.lpfnWndProc   = WndProc;  
    wc.cbClsExtra    = 0;  
    wc.cbWndExtra    = 0;  
    wc.hInstance     = hInstance;  
    wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FULLSCREEN));  
    wc.hCursor       = 0;  
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);  
    wc.lpszMenuName  = 0;  
    wc.lpszClassName = szWindowClass;  
 
    return RegisterClass(&wc);  
}  
 
// 
//   FUNCTION: InitInstance(HINSTANCE, int) 
// 
//   PURPOSE: Saves instance handle and creates main window 
// 
//   COMMENTS: 
// 
//        In this function, we save the instance handle in a global variable and 
//        create and display the main program window. 
// 
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)  
{  
    HWND hWnd;  
    TCHAR szTitle[MAX_LOADSTRING];      // title bar text 
    TCHAR szWindowClass[MAX_LOADSTRING];    // main window class name 
 
    g_hInst = hInstance; // Store instance handle in our global variable 
 
    // SHInitExtraControls should be called once during your application's initialization to initialize any 
    // of the device specific controls such as CAPEDIT and SIPPREF. 
    SHInitExtraControls();  
 
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);   
    LoadString(hInstance, IDC_FULLSCREEN, szWindowClass, MAX_LOADSTRING);  
 
    //If it is already running, then focus on the window, and exit 
    hWnd = FindWindow(szWindowClass, szTitle);    
    if (hWnd)   
    {  
        // set focus to foremost child window 
        // The "| 0x00000001" is used to bring any owned windows to the foreground and 
        // activate them. 
        SetForegroundWindow((HWND)((ULONG) hWnd | 0x00000001));  
        return 0;  
    }   
 
    if (!MyRegisterClass(hInstance, szWindowClass))  
    {  
        return FALSE;  
    }  
 
    hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,  
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);  
 
    if (!hWnd)  
    {  
        return FALSE;  
    }  
 
    // When the main window is created using CW_USEDEFAULT the height of the menubar (if one 
    // is created is not taken into account). So we resize the window after creating it 
    // if a menubar is present 
    if (g_hWndMenuBar)  
    {  
        RECT rc;  
        RECT rcMenuBar;  
 
        GetWindowRect(hWnd, &rc);  
        GetWindowRect(g_hWndMenuBar, &rcMenuBar);  
        rc.bottom -= (rcMenuBar.bottom - rcMenuBar.top);  
          
        MoveWindow(hWnd, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, FALSE);  
    }  
      
    ShowWindow(hWnd, nCmdShow);  
    //MakeFullScreen(hWnd); 
    UpdateWindow(hWnd);  
      
      
    return TRUE;  
}  
 
// 
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) 
// 
//  PURPOSE:  Processes messages for the main window. 
// 
//  WM_COMMAND  - process the application menu 
//  WM_PAINT    - Paint the main window 
//  WM_DESTROY  - post a quit message and return 
// 
// 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
{  
    int wmId, wmEvent;  
    PAINTSTRUCT ps;  
    HDC hdc;  
    LPWINDOWPOS pos;  
 
    static SHACTIVATEINFO s_sai;  
      
    switch (message)   
    {  
        case WM_COMMAND:  
            wmId    = LOWORD(wParam);   
            wmEvent = HIWORD(wParam);   
            // Parse the menu selections: 
            switch (wmId)  
            {  
                case IDM_HELP_ABOUT:  
                    DialogBox(g_hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, About);  
                    break;  
                case IDM_OK:  
                    SendMessage (hWnd, WM_CLOSE, 0, 0);               
                    break;  
                default:  
                    return DefWindowProc(hWnd, message, wParam, lParam);  
            }  
            break;  
        case WM_CREATE:  
            SHMENUBARINFO mbi;  
 
            memset(&mbi, 0, sizeof(SHMENUBARINFO));  
            mbi.cbSize     = sizeof(SHMENUBARINFO);  
            mbi.hwndParent = hWnd;  
            mbi.nToolBarId = IDR_MENU;  
            mbi.hInstRes   = g_hInst;  
 
            if (!SHCreateMenuBar(&mbi))   
            {  
                g_hWndMenuBar = NULL;  
            }  
            else 
            {  
                g_hWndMenuBar = mbi.hwndMB;  
            }  
 
            // Initialize the shell activate info structure 
            memset(&s_sai, 0, sizeof (s_sai));  
            s_sai.cbSize = sizeof (s_sai);  
            break;  
        case WM_PAINT:  
              
            hdc = BeginPaint(hWnd, &ps);  
              
            // TODO: Add any drawing code here... 
              
            EndPaint(hWnd, &ps);  
            break;  
        case WM_DESTROY:  
            CommandBar_Destroy(g_hWndMenuBar);  
            PostQuitMessage(0);  
            break;  
        case WM_WINDOWPOSCHANGED: 
           pos = (LPWINDOWPOS) lParam;  
            if ((pos->cx != usedRect.right - usedRect.left) ||  
                (pos->cy != usedRect.bottom - usedRect.top))  
            {  
                MakeFullScreen(hWnd);  
            }  
            break;
  
        case WM_ACTIVATE:  
               
            // Notify shell of our activate message 
             SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);  
            break;  
        case WM_SETTINGCHANGE:  
            SHHandleWMSettingChange(hWnd, wParam, lParam, &s_sai);  
            break;  
 
        default:  
            return DefWindowProc(hWnd, message, wParam, lParam);  
    }  
    return 0;  
}  
 
// Message handler for about box. 
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
{  
    switch (message)  
    {  
        case WM_INITDIALOG:  
            {  
                // Create a Done button and size it.   
                SHINITDLGINFO shidi;  
                shidi.dwMask = SHIDIM_FLAGS;  
                shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;  
                shidi.hDlg = hDlg;  
                SHInitDialog(&shidi);  
            }  
            return (INT_PTR)TRUE;  
 
        case WM_COMMAND:  
            if (LOWORD(wParam) == IDOK)  
            {  
                EndDialog(hDlg, LOWORD(wParam));  
                return TRUE;  
            }  
            break;  
 
        case WM_CLOSE:  
            EndDialog(hDlg, message);  
            return TRUE;  
 
    }  
    return (INT_PTR)FALSE;  
}  



Tuesday, April 14, 2009 3:04:43 PM (Central Europe Standard Time, UTC+01:00)       
Comments [8]  Compact .Net Framework | Nativní kód


 Friday, March 20, 2009
GSM Net Monitor verze 0.10.0

 NMPodrobnostiHomepage aplikace.

Instalační cab.

Návod na rozchození lokalizace pozice pomocí BTS

Paypal donate

Změny ve verzi  0.10.0.

  1. Odstraněna chyba, která se mohla projevit tím, že se v detailech o cell id, které se načítají z csv souborů, nemusely některé informace zobrazit, i když v csv souboru informace byla. Děkuji uživateli "Santana” za report chyby.
  2. Interní změny v  implementaci  “observera” RIL vrstvy.


Důležité:

Před instalací nové verze vypněte v aplikaci sledování sítě. Nejlépe starou verzi také sami deaktivujte v nastavení Today obrazovky a odnistalujte ji přes applet Přidat-Odebrat programy.

Protože se jedná o AlFA preview, doporučuji před instalaci Net Monitoru mít v zařízení např. SPB Pocket Plus a v něm aktivovaný safe boot - jestliže by vám "vytuhlo" zařízení, nebudete muset dělat HR (Hard Reset), protože můžete při startu zařízení dočasně deaktivovat Today pluginy.

Program pro jistotu ani nezkoušejte na zařízení, kde je nahráno TouchFlo, nebo jiný agresivní Today plugin. Riskujete zatuhnutí zařízení a je zbytečné mi potom psát dojemné maily, pokud nejste schopni si předtím udělat zálohu PDA nebo alespoň mít funkční safe-boot.

Po upgradu budete muset pravděpodobně znovu zadat svůj registrovaný email a přístupový kód. Pokud jste jej zapomněli, jděte na stránku http://gsmadmin.renestein.net a zadejte znovu svůj email. Aplikace vám nabídne opětovné zaslání emailu s přístupovým kódem.

Jestliže máte zařízení s VGA displejem, v pluginu jsou malé ikony a plugin je vykreslován na malé ploše. Plugin může být vykreslen na větší ploše  - přes kontexové menu zobrazte Nastavení pluginu a změňte na záložce Základní nastavení preferovanou výšku na obrazovce Dnes. Ikony ale stejně zůstanou malé, proto chystám plnohotnotnou VGA verzi, do té doby lze plugin plně ovládat přes kontextové menu.

NetM1



Friday, March 20, 2009 11:16:56 AM (Central Europe Standard Time, UTC+01:00)       
Comments [0]