Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

21/05/2015

What every blogger should do if using someone else's code #2

Home

This time I'd like to write about WPFLocalizationExtension library which makes localization of WPF applications easy. I've been using it for 4 years in my projects and it simply works. To be honest I've just realized that the version I'm using is considerably outdated. However, for all this time I haven't encountered problems that would make me to download a newer version of WPFLocalizationExtension.

I think that it is a quite good recommendation. So, if you work with WPF and you need to localize your application I encourage to give a chance to WPFLocalizationExtension.

18/04/2015

What every blogger should do if using someone else's code #1

Home

The developer's work very often involves effective re-using of a code that was written by someone else. Sometime these are fully fledged libraries or frameworks but sometimes these are also small or even very small pieces of code found on a blog or a forum. All this stuff makes our life easier.

When I use someone else's code in my project I wonder if it is worth writing about it on my blog. And sometimes I hesitate. Why? Because it is a well know library and there is a lot about it in Internet. Sometimes because I don't have time to write a tutorial. And sometimes because I think that there is no much to write about,that it is a small piece of code so everybody can understand it on their own.

Now, I think that it is a wrong approach. Maybe it is small, but I used it so it means that I din't have to design, write and test it on my own. Maybe I don't have time to write a tutorial but this code actually saved me time that I'm lacking.

So, what should I (we) do? Just write a short note on our blogs and say that we used this and this in our projects. At least in this way we can thank the authors.

To start. In WPF there is a class GridSplitter that allows user to resize rows and columns in a grid. However, I wanted the same functionality for DockPanel but WPF doesn't provide it. I decided that I'll wrote it but firstly I goggled for something like that and I found this article. The DockPanelSplitter class has 300 lines of code, it is not complex but works well. Give it a chance.

06/10/2014

OxyPlot i nieciągłe przedziały

Home

Od dłuższego czasu używam biblioteki OxyPlot i bardzo ją sobie chwalę. Ostatnio zamarzyło mi się stworzenie wykresu przedziałowego. Na początek stwierdziłem, że wystarczy zastosować zwykły wykres liniowy czyli klasę LineSeries. Załóżmy, że chcemy zaprezentować na wykresy przedziały tj.; (2,3), (4,6) oraz (8,10). Spróbujmy, więc czegoś takiego:
var s = new LineSeries();
s.Title = "Nieciągłe przedziały";
s.Points.Add(new DataPoint(2, 1));
s.Points.Add(new DataPoint(3, 1));
           
s.Points.Add(new DataPoint(4, 1));
s.Points.Add(new DataPoint(6, 1));
        
s.Points.Add(new DataPoint(8, 1));
s.Points.Add(new DataPoint(10, 1));
To jednak nie zadziała gdyż w rezultacie otrzymamy linię ciągłą, czego zresztą należało się spodziewać ponieważ LineSeries po prostu łączy kolejne punkty. Wypróbowałem więc inne rodzaje wykresów, bawiłem się ustawieniami, ale bez rezultatów. Rozwiązanie okazało się jednak bardzo proste. Jeśli nie chcemy, aby dwa punkty zostały połączone linią to pomiędzy nimi należy umieścić punkt o współrzędnych (Double.Nan, Double.NaN).
var s = new LineSeries();
s.Title = "Nieciągłe przedziały";
s.Points.Add(new DataPoint(2, 1));
s.Points.Add(new DataPoint(3, 1));
s.Points.Add(new DataPoint(Double.NaN, Double.NaN));            
s.Points.Add(new DataPoint(4, 1));
s.Points.Add(new DataPoint(6, 1));
s.Points.Add(new DataPoint(Double.NaN, Double.NaN));       
s.Points.Add(new DataPoint(8, 1));
s.Points.Add(new DataPoint(10, 1));
Na koniec jeszcze przykład tak skonstruowanego wykresu:

17/05/2014

Przydaś do rysowania wykresów

Home

Od jakiegoś czasu sporo pracuję z różnymi seriami danych numerycznych np.: sekwencje identyfikatorów metod wywołanych w programie. Serie takie mogą być bardzo długie i potrzebowałem narzędzia, które łatwo i szybko pozwoli mi je wizualizować czyli wygenerować wykres, tak abym mógł szybko ocenić co znajduje się w danym pliku. Kombajnów do rysowania wykresów jest dużo, choćby Excel, ale ja potrzebowałem czegoś jeszcze prostszego. Idealnie abym mógł po prostu zaznaczyć plik z danymi i z menu kontekstowego wybrać polecenie Narysuj wykres. Możliwe, że znalazłbym coś takiego, ale napisanie takie narzędzia samemu zajęło mi dosłownie chwilę.

Po pierwsze zdecydowałem się użyć biblioteki OxyPlot bo jest bardzo prosta w użyciu, a także radzi sobie z długimi seriami danych i pozwala narysować wiele różnych typów wykresów. W drugim kroku stworzyłem bardzo prostą aplikację z jednym oknem w WPF'ie. Do projektu dodałem referencje do bibliotek OxyPlot oraz OxyPlot.Wpf. XAML dla głównego i jedynego okna wygląda w następujący sposób:
<Window x:Class="DrawPlot.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:oxy="http://oxyplot.codeplex.com"
        xmlns:local="clr-namespace:DrawPlot"
        Title="DrawPlot" Height="350" Width="525">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <oxy:Plot Model="{Binding MyModel}"/>
    </Grid>
</Window>
Trzeci krok to napisanie klasy MainViewModel, która stworzy i zainicjuje obiekt klasy PlotModel. Obiekt ten będzie zawierał informacje potrzebne do narysowania wykresu i przy pomocy binding'u zostanie przekazany do kontrolki Plot. W podstawowej wersji kod tej klasy jest bardziej niż prosty. Zakładam z góry określony format pliku wejściowego tj. każda linia zawiera jedną liczbę, a część ułamkowa musi być oddzielona od części całkowitej kropką. Użyłem klasy LineSeries czyli rysuję wykres liniowy. W poniższym kodzie celowo pominąłem również bardziej przyjazną obsługę błędów czyli jeśli coś się nie powiedzie to po prostu wyświetlam treść błędu i zamykam aplikację.
namespace DrawPlot
{
    public class MainViewModel
    {
        public PlotModel MyModel { get; private set; }

        public MainViewModel()
        {
            try
            {
                var args = Environment.GetCommandLineArgs();

                var lines = File.ReadAllLines(args[1]);

                var lineSeries = new LineSeries();
                for (var i = 0; i < lines.Length; ++i)
                    lineSeries.Points.Add(new DataPoint(i, Double.Parse(lines[i], CultureInfo.InvariantCulture)));

                var model = new PlotModel(Path.GetFileName(args[1]));
                model.Series.Add(lineSeries);
                MyModel = model;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                Application.Current.Shutdown();
            }
        }
    }
}
Ostatni krok to dodanie nowej pozycji do menu kontekstowego. Uzyskałem to przy pomocy dodania odpowiedniego wpisu w rejestrze. W moim przypadku skompilowana aplikacja znajduje się w lokalizacji T:\bin\DrawPlot\DrawPlot.exe.
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\Shell\Draw plot]

[HKEY_CLASSES_ROOT\*\Shell\Draw plot\command]
@="T:\\bin\\DrawPlot\\DrawPlot.exe \"%1\""
Narzędzie jest bardzo proste, ale spełnia swoje zadanie. Możne je również łatwo rozbudować na przykład użyć innego rodzaju serii aby narysować wykres kołowy, dodać obsługę innych formatów plików wejściowych itd.

08/07/2012

WPF i opóźnione wykonanie (deferred execution)

Home

Najpierw spójrzmy na poniższy fragment XAML'a z prostą implementacją combobox'a z wieloma kolumnami:

...
<ComboBox ItemsSource="{Binding PerformedAnalysis}" SelectedValuePath="Id" SelectedValue="{Binding SelectedItem}">
 <ComboBox.ItemTemplate>
  <DataTemplate>
   <StackPanel Orientation="Horizontal">
    <Border BorderThickness="1" BorderBrush="Black" Margin="1" Padding="1">
     <TextBlock Text="{Binding Id}" Width="100"></TextBlock>
    </Border>
    <Border BorderThickness="1" BorderBrush="Black" Margin="1" Padding="1">
     <TextBlock Text="{Binding Name}" Width="500"></TextBlock>
    </Border>
   </StackPanel>
  </DataTemplate>
 </ComboBox.ItemTemplate>
</ComboBox>
...

Oraz kod z view model'u zasilający tą kontrolkę. Kod ten używa Entity Framework, a Context to nic innego jak obiekt dziedziczący po ObjectContext.

...
private IEnumerable _PerformedAnalysis = null;

public IEnumerable PerformedAnalysis
{
    get
    {
        try
        {
            if (_PerformedAnalysis == null)
            {
                _PerformedAnalysis = 
                        from a in Context.Analyses
                        orderby a.Id
                        select new { Id = a.Id, Name = a.Name };
            }
        }
        catch (Exception ex)
        {
            ServiceProvider.GetService<IWindowService>().ShowError(ex);
            _PerformedAnalysis = new[] { new { Id = -1, Name = ex.Message } };
        }

        return _PerformedAnalysis;
    }
}
...

Kod ten zawiera dość poważną usterką. Jaką? Ktoś może powiedzieć, że błędem jest użycie bezpośrednio EF'a, zamiast ukryć dostęp do danych za warstwą interfejsów. W prostej aplikacji nie ma to moim zdaniem sensu, w bardziej rozbudowanej też można nad tym dyskutować. Ktoś może również powiedzieć, że przy bindowaniu należy użyć ObservableCollection. W tym jednak przypadku do kolekcji nie będą dodawane lub usuwane żadne elementy, więc nie jest to konieczne.

Błąd jest dużo poważniejszy i może spowodować, potocznie mówiąc, wywalenie się całej aplikacji. Odpowiedzmy na pytanie co się stanie jeśli nawiązanie połączenia z bazą danych jest niemożliwie? Oczywiście zostanie zgłoszony błąd ale czy zostanie obsłużony? Pozornie powyższy kod poradzi sobie z takim scenariusz, przecież zawiera blok try/catch.

Zapomniano jednak o tzw. opóźnionym wykonaniu (ang. deffered execution) czyli o tym, że właściwe zapytanie do bazy danych zostanie wykonane dopiero kiedy dane będą rzeczywiście potrzebne czyli kiedy silnik WPF wykona bindowanie. Inaczej mówiąc wyjątek spowodowany brakiem połączenia z bazą danych zostanie rzucony gdzieś wewnątrz silnika WPF. Będzie można go przechwycić korzystając z DispatcherUnhandledException ale z perspektywy działania aplikacji to już nic nie da.

Jak to często bywa naprawienie błędu jest bardzo proste. Należy zapewnić wykonanie zapytania jeszcze w naszym kodzie np.:

...
 _PerformedAnalysis = 
  (from a in Context.Analyses
  orderby a.Id
  select new { Id = a.Id, FileName = a.FileName, Start = a.StartTime }).ToList();
...

05/06/2011

Aplikacje wielojęzyczne - WPF

Home

Przystępując do tłumaczenia aplikacji WPF miałem dokładny plan jak się za to zabrać. Mianowicie postanowiłem użyć narzędzia LocBaml, o którym dowiedziałem się z training kit'a do egzaminu 70-502. Opis całej procedury można znaleźć tutaj. Praktyka pokazała jednak, że narzędzie to pozostawia bardzo dużo do życzenia. Dalej opiszę kolejne kroki pracy z LocBaml wraz z komentarzem jak to wygląda w praktyce.

UIDs

Mechanizm wielojęzyczności aplikacji w WPF koncepcyjnie zbliżony jest to tego co znamy z ASP.NET. W szczególności każda kontrolka, która posiada jakieś zasoby do przetłumaczenia powinna mieć odpowiedni identyfikator tzw. UID. Identyfikatory te można nadać automatycznie przy pomocy polecenia:

msbuild /t:updateuid NAZWA_PROJEKTU.csproj

Wszystkie pliki XAML, które mają zostać przetworzone przez msbuild powinny być checkoutowane (brzmi okropnie ale nie przychodzi mi do głowy dobry polski odpowiednik). Narzędzie to działa i dobrze i źle. Dobrze bo rzeczywiście wygeneruje dla wszystkich kontrolek UID. Źle bo zrobi to dla wszystkich kontrolek, a właściwie powinienem napisać dla wszystkich elementów dokumentu XAML. msbuild nie wykonuje jakiejkolwiek analizy przetwarzanych elementów. Skutkuje to dużym bałaganem. Po przeprowadzeniu tej operacji w plikach XAML pojawi się bardzo dużo, niepotrzebnych, śmieciowych UID'ów, na przykład po co nadawać identyfikator UID kontrolce Border, Line lub StackPanel. Potem aż bolą oczy jak się patrzy na tak przetworzony XAML. Moja rada jest taka. Jeśli chcemy stosować LocBaml to zawczasu, tworząc interfejs użytkownika, powinniśmy nadawać kontrolkom UID'y.

Wyciąganie zasobów do przetłumaczenia

Po wygenerowaniu UID'ów przechodzimy do następnego kroku czyli używamy LocBaml aby wyciągnać zasoby do przetłumaczenia do pliku CSV. Służy do tego takie polecenie:

LocBaml.exe /parse NAZWA_PROJEKTU.resources.dll /out:NAZWA_PLIKU_WYJSCIOWEGO.CSV

Skompilowane zasoby czyli pliki dll umieszczane są w podkatalogach o nazwach zgodnych z kulturą (językiem) zasobów np.: en-US, pl-PL itp. Jeśli nasz projekt nazywa isę SimpleApplication to plik dll z zasobami będzie nazywał się SimpleApplication.resources.dll. W praktyce wydanie tego polecenia skończy się błędem:

Could not load file or assembly '...' or one of its dependencies. The system cannot find the file specified.

Rozwiązanie jest proste ale trochę upierdliwe. Otóż plik LocBaml.exe, plik z zasobami NAZWA_PROJEKTU.resources.dll oraz plik exe/dll powstały po skompilowaniu aplikacji/biblioteki muszą znajdować się w tym samym folderze. Najszybciej to oskryptować. Podobny ale trochę inny komunikat o błędzie:

Could not load file or assembly '...' or one of its dependencies. An attempt was made to load a program with an incorrect format.

Otrzymamy jeśli nasza aplikacja ma ustawioną docelową platformę na x86. W takim wypadku możemy zmienić ustawienia naszej aplikacji, przekompilować LocBaml na x86 albo użyć narzędzia corflags o czym już zresztą pisałem tutaj lub tutaj.

Tak wygenerowany plik CSV powinniśmy teraz przetłumaczyć. Jeśli użyliśmy msbuild do wygenerowania UID'ów to plik ten będzie zawierał dla dużej aplikacji nawet kilkanaście tysięcy wierszy. W moim przypadku było to około 13 tysięcy wierszy z czego jakieś 10% wymagało przetłumaczenia!!! Po prawdzie tak duża liczba wierszy jest również związana ze sposobem działania LocBaml. Otóż dla każdej kontrolki z UID'em w pliku CSV znajdziemy wiele wierszy. Na przykład dla kontrolki TextBlock możemy zlokalizować, co oczywiste, właściwość Text ale również mniej sensowne jak Foreground czy Margin.

Stworzenie pliku dll z przetłumaczonymi zasobami

To jest chyba najtrudniejszy, a zarazem najmniej wygodny krok w całym procesie. W teorii jest to proste. Bierzemy przetłumaczony plik CSV i używamy LocBaml do wygenerowania dll'ki z zasobami o nazwie NAZWA_PROJEKTU.resources.dll, a następnie umieszczamy ją w odpowiednim katalogu aplikacji np.: en-US. Za wczytanie odpowiedniej wersji zasobów odpowiada już silnik WPF, a decyzje podejmuje podobnie jak w ASP.NET czy WinForms na podstawie właściwości Thread.CurrentThread.CurrentUICulture.

Problem pierwszy związany jest z bugiem w implementacji LocBaml, który objawia się tym, że identyfikatory zasobów w pliku CSV mogą się powtarzać. W związku z tym proces odwrotny czyli wygenerowanie dll'ki na podstawie pliku CSV się nie powiedzie. Rozwiązanie problemu można znaleźć tutaj i jest ono trywialne ale wymaga rekompilacji projektu. Można też, ale tego nie próbowałem, zapewnić, że wszystkie pliki XAML w naszym projekcie mają inne nazwy nawet jeśli znajdują się w innych katalogach.

Pierwszy problem w porównaniu z drugim to nic. Wyobraźmy sobie taką sytuację. Mamy projekt aplikacji WPF i grzecznie, zgodnie z zasadami wszystkie komunikaty wyświetlane użytkownikowi trzymamy w plikach zasobów resx. Po jakim czasie chcemy przygotować angielską wersję aplikacji. Zaczynamy od przetłumaczenia tych zasobów, a więc plik np.: msg.resx kopiujemy i zmieniamy mu nazwę na msg.en-US.resx, a następnie tłumaczymy.

Teraz zabieramy się za XAML. Nadajemy kontrolką UID'y, wyciągamy zasoby do pliku CSV, tłumaczymy i ponownie używamy narzędzie LocBaml do wygenerowania dll'ki z zasobami. Wszystko poszło jak po maśle i zadowoleni chcemy skopiować świeżutką dll'ke z przetłumaczonymi zasobami do katalogu en-US, a tu figa z makiem. W katalogu znajduje się już dll'ka o takiej nazwie. Skąd się wzięła? Została wygenerowana przez VS i zawiera komunikaty, które umieściliśmy w pliku msg.en-US.resx. Mamy więc dwie dll'ki o takiej samej nazwie, jedną z przetłumaczonymi komunikatami, a drugą z przetłumaczonym GUI i musimy umieścić je w tym samym katalogu.

I tutaj zaczynają się schody, wysokie, wąskie, ciemne i niewygodne. Nie pozostaje nic innego jak użyć linkera al.exe. Da się to zrobić ale tak jak powiedziałem jest to bardzo niewygodne. Nie będę tutaj tego opisywał bo moim zdaniem jest to strasznie nudne. Jeśli ktoś tego potrzebuje to zapraszam do kontaktu.

Inne

Takie podejście do lokalizowania aplikacji WPF ma również inne wady. Tak jak wspomniałem wersja zasobów jaka zostanie wczytana zależy od właściwości Thread.CurrentThread.CurrentUICulture. Trzeba więc pamiętać aby ustawić ją na odpowiednią kulturę zanim zaczniemy robić cokolwiek z interfejsem użytkownika. W przeciwnym wypadku możemy doprowadzić do wczytania złej wersji zasobów i okaże się, że cześć okien będzie w języku polskim, a część w angielskim. Po drugie, modyfikacja Thread.CurrentThread.CurrentUICulture nie powoduje automatycznego przełączenia się pomiędzy jedną, a drugą wersją zasobów. Dopiero zamknięcie okna i ponowne jego wyświetlenie spowoduje pobranie odpowiedniej wersji językowej zasobów.

Podsumowanie

LocBaml można użyć, pytanie czy z wszystkimi wadami i ograniczeniami tego narzędzia warto. W omawianym przypadku skończyło się to użyciem wyszukanej przez kolegę biblioteki WPFLocalizeExtension. Też ma swoje wady, też nie jest idealna, w szczególności w żaden sposób nie automatyzuje procesu lokalizowania aplikacji ale nie wymaga UID'ów, nie trzeba pisać skryptów, linkować itd. Tutaj zresztą kłania się to co już pisałem dwa razy:

Jeśli chcemy aby nasza aplikacja miała wiele wersji językowych to przygotowujmy się do tego od pierwszej linijki tej aplikacji.


01/04/2011

Zabawy z pozycjonowaniem okna w WPF

Home

Czasami zachodzi potrzeba "ręcznego" pozycjonowania okna w aplikacji WPF. Sprawa jest prosta i sprowadza się do odpowiedniego ustawienia właściwości Left oraz Top. Jeśli chcemy aby pozycja okna dziecka zależała od położenia rodzica to możemy odwołać się do rodzica przez właściwość Owner i napisać taki kod jak poniżej. Kod ten powoduje przesunięcie okno dziecka o 50 jednostek w prawo względem lewej krawędzi okna rodzica.
...
myWindow.Left = myWindow.Owner.Left + 50;
...
Nic prostrzego. Jest tu jednak mały haczyk. Jeśli okno rodzica jest zmaksymalizowane to właściwość Left i Top dla rodzica będą miały wartość wskazującą położenie okna przed zmaksymalizowaniem. Poniżej kod, który to uwzględnia:
...
myWindow.Left = (myWindow.Owner.WindowState == WindowState.Maximized ? 0 : myWindow.Owner.Left) + 50;
...
Nic trudnego ale trzeba o tym pamiętać. Sprawa robi się jeszcze ciekawsza kiedy pracujemy z wieloma monitorami. W takim wypadku powyższy kod będzie działał tylko dla głównego monitora. Jeśli okno rodzica będzie zmaksymalizowane i będzie znajdować się na innym monitorze niż główny to wykonanie tego kodu spowoduje wyświetlenie okna dziecka na głównym monitorze! Poniżej rozwiązanie tego problemu:

if (myWindow.Owner.WindowState == WindowState.Maximized)
{
  Screen s = Screen.FromHandle(new WindowInteropHelper(myWindow.Owner).Handle);                      
  myWindow.Left = s.WorkingArea.Left + 50;
}
else
{
  myWindow.Left = myWindow.Owner.Left + 50;
}
                    
Korzystam tutaj ze starej poczciwej klasy Screen, przy pomocy której znajduję monitor, na którym wyświetlane jest okno rodzica. Następnie odczytuję położenie lewej krawędzi tego monitora. Wartość ta będzie różna w zależności od tego, z którym monitorem mamy do czynienia.

16/02/2011

DockPanel i ScrollViewer

Home

Dlaczego lubię kontener DockPanel? Ponieważ dobrze (intuicyjnie) współpracuje z kontrolką ScrollViewer, która dostarcza pionowych i poziomych pasków przewijania. Załóżmy, że główne okno naszej aplikacji zawiera listę (kontrolka ListView) z kilkuset wierszami. Wierszy jest na tyle dużo, że w danym momencie na ekranie widoczna jest tylko część z nich. Aby użytkownik mógł przewijać listę i zobaczyć wszystkie wiersze używamy kontrolki ScrollViewer

ScrollViewer zachowa się jednak inaczej w zależności od kontenera w jakim zostanie osadzony. Jeśli będzie to np.: StackPanel lub Grid to paski przewijania nie zostaną wyświetlone i otrzymamy efekt jak poniżej.



Można sobie z tym poradzić ustawiając na kontrolce ScrollViewer właściwość Height ale spowoduje to, że lista będzie miała zawsze tą samą wysokość bez względu na wielkość okna. Podobnie można ustawić właściwość Height dla listy ale efekt będzie taki sam.



Dużo lepszym rozwiązaniem jest osadzenia kontrolki ScrollViewer wewnątrz kontenera DockPanel. Dzięki temu uzyskamy efekt jak poniżej. Paski przewijania są widoczne, a wysokość listy zależy od wielkości okna.


03/09/2009

ESRI WPF/Silverlight API

Home

ESRI to lider światowego rynku systemów informacji geograficznej (ang. GIS). Lider przez duże L - Na całym świecie z rozwiązań ESRI korzysta 300 tyś instytucji w tym 2/3 firm z listy Fortune. ESRI to taki Microsoft w świecie GIS'ów :). Czym jednak są systemy GIS'owe?

GIS to w bardzo dużym skrócie system służący do wprowadzania, przechowywania, przetwarzania, analizowania i wizualizowania danych przestrzennych. Upraszczając jeszcze bardziej, a wręcz trywializując chodzi tu po prostu o mapę ;) Niektórzy zapewne pomyślą coś w rodzaju: "Czym ty zawracasz głowę człowieku, przecież jest już Google Maps". Nie obrażając nikogo Google Maps w porównaniu z produktami ESRI to zabawka dla dzieci, którą poważni ludzie się nie zajmują. Patrząc na to z innej strony oferta ESRI (w jednym z kolejnych postów postaram się ją przybliżyć) jest dużo, dużo, dużo bogatsza. Poza tym jest skierowana do zupełnie innego rodzaju odbiorców, którzy poza wizualizacją danych geograficznych chcą je analizować i przetwarzać w celu usprawnienia swoich procesów biznesowych.

Czemu jednak w ogóle o tym piszę? Otóż rozwiązania ESRI są generalnie bardzo drogie i dostępnie dla niewielkiego grona specjalistów, a co z tym związane mało znane. Ostatnio dotarłem jednak do darmowego (dla celów niekomercyjnych) API dla WPF/Silverlight umożliwiającego tworzenie całkiem fajnych aplikacji. Tworzone aplikacje możemy zasilić danymi udostępnianymi przez ESRI poprzez ArcGIS Online. Zasoby udostępnione publicznie nie są tak duże jak w przypadku Google ale można już coś zdziałać przy ich pomocy. Możliwe jest również wykorzystanie map publikowanych przez Microsoft na Bing Maps.

Potrzebne biblioteki można pobrać tutaj, a dokumentację znajdziemy tutaj. Początkowo chciałem opisać prosty przykład użycia ale zrezygnowałem z tego kiedy zobaczyłem tą stronę. Znajdziecie tam kilkanaście bardzo prostych, poglądowych aplikacji. Polecam.