WPF-XAML bindolas

WPF-XAML bindolas
2007-01-02T18:27:56+01:00
2007-01-08T19:28:59+01:00
2022-11-02T20:25:50+01:00
wjuszko
Sziasztok...

Ismerkedem a WPF-el es rogron beleakadtam egy problemaba amit nemtudok megoldani. Egy programot keszitettem, ami kepeket jelenit meg <XmlDataProvider> -t hasznalva a kepek tualjdonsagainak tarolasara:
Ilyen modon:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <XmlDataProvider x:Key="Thingek" XPath="Thingek/Thing"> <x:XData> <Thingek xmlns=""> <Thing Image="DSCF0249.JPG"/> <Thing Image="DSCF0250.JPG"/> <!-- <Thing Image="DSCF0251.JPG"/> <Thing Image="DSCF0252.JPG"/> <Thing Image="DSCF0253.JPG"/> <Thing Image="DSCF0254.JPG"/> <Thing Image="DSCF0255.JPG"/> <Thing Image="DSCF0256.JPG"/> <Thing Image="DSCF0257.JPG"/> <Thing Image="DSCF0260.JPG"/> <Thing Image="DSCF0262.JPG"/> <Thing Image="DSCF0263.JPG"/> --> </Thingek> </x:XData> </XmlDataProvider> <!-- <XmlDataProvider x:Key="Kepek" XPath="kepDir"> <x:XData> <kepDir xmlns=""> <Thing Image="c:\\Kepek"/> </kepDir> </x:XData> </XmlDataProvider> --> </ResourceDictionary>

Namarmost ez igy nem tul hasznalhato, mert csak azokat jelniti meg amiket ide irok. Ugy szeretnem megoldani, hogy C# osztaly egy konyvtarat figyel amikben a kepek vannak:


using System; using System.Collections.Generic; using System.Collections; using System.Text; using System.IO; namespace KonyvtarBeolvas { class KonyvtarBeolvaso { private ArrayList KepekPath = new ArrayList(); private string DirPath = String.Empty; public ArrayList Paths { get{return KepekPath;} } public KonyvtarBeolvaso(string path) { if (Directory.Exists(path)) { DirPath = path; pathsBeallito(); FileSystemWatcher watcher = new FileSystemWatcher(DirPath); watcher.EnableRaisingEvents = true; watcher.IncludeSubdirectories = true; watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName; watcher.Filter = "*.jpg"; watcher.Changed += new FileSystemEventHandler(Valtozott); watcher.Created += new FileSystemEventHandler(Valtozott); watcher.Deleted += new FileSystemEventHandler(Valtozott); } else throw new DirectoryNotFoundException(); } private void Valtozott(object source, FileSystemEventArgs e) { pathsBeallito(); } private void pathsBeallito() { foreach (string file in Directory.GetFiles(DirPath)) { if (file.ToLower().EndsWith(".jpg")) { KepekPath.Add(file.ToString()); } } } } }

Ezeket a kepeket szeretnem BIND-olni a XAML-be, de sehogy sem megy...

Valakinek van valami otlete? Elore is koszonom!
Mutasd a teljes hozzászólást!
A reggel összedobtam. A jó hír az, hogy nem kell sza...szórakozni a Dispatcher átadásával, mert a Dispatcher osztálynak van egy statikus CurrentDispatcher tulajdonsága. Szóval a Te eredeti XAML kódod úgy jó, ahogy van.

<Application.Resources> <local:KonyvtarBeolvas x:Key="Kepbeolvasas"/> <CollectionViewSource x:Key="Kepek" Source="{StaticResource Kepbeolvasas}"/> </Application.Resources>

Az OkumlacijosCollection-t pedig így kell megcsinyáni (ezek az én osztályelnevezéseim):

public class ObservableCollectionMT<T> : ObservableCollection<T> { private Dispatcher dispatcherUIThread; private delegate void SetItemCallback(int index, T item); private delegate void RemoveItemCallback(int index); private delegate void ClearItemsCallback(); private delegate void InsertItemCallback(int index, T item); private delegate void MoveItemCallback(int oldIndex, int newIndex); public ObservableCollectionMT() { this.dispatcherUIThread = Dispatcher.CurrentDispatcher; } protected override void SetItem(int index, T item) { if (dispatcherUIThread.CheckAccess()) { base.SetItem(index, item); } else { dispatcherUIThread.BeginInvoke(DispatcherPriority.Send, new SetItemCallback(SetItem), index, item); } } // Similar code for RemoveItem, ClearItems, InsertItem and MoveItem (...) }

Ebből thread safe BitmapImage collectiont, ami neked kell, így származtatod (az MT nálam a multi-threadinget jelenti):

public class BitmapImageCollectionMT : ObservableCollectionMT<BitmapImage> { }

OK. Van egy thread safe bitmap kolleckciód, ebből már lehet is gyártani a Watchert:

public class JpgWatcher : BitmapImageCollectionMT { . . . }

Ebbe nyugodtan teheted a FileSystemWatchert, ami úgy variálja a kollekciót, ahogy akarja, nem fog összeakadni a dolog az UI thread-del.

Ami még kiderült az az, hogy a PropertyChanged eseményeket a WPF képes szálbiztosan kezelni, ott ezzel nem kell már foglalkozni. Je.
Mutasd a teljes hozzászólást!

  • Itt a lap alján megtalálod az MSDN konferencia anyagát (előadások + demók).
    Ott egy egyszerű class segítségével mutatták be az xaml képességeit és a bindingot (talán 3 usert adott vissza statikusan).
    Innen szerintem el tudsz indulni.
    Mutasd a teljes hozzászólást!
  • Koszi.. ezen mar el tudok indulni!
    Nemnagyon tolonganak az emberek ebben a topicban
    Mutasd a teljes hozzászólást!
  • [off]
    Rakj fel egy rtfm php kérdést. Lesz itt tolongás :)
    Mutasd a teljes hozzászólást!
  • Talaltam sok info-t viszont a dynamicBinding-rol nem sokat.

    Jol ertem, hogy erre lenne szuksegem akkor, ha egy ObservableCollection-t szeretnek bindolni az xaml-be ami hogyha valtozik az ObservableCollection tartalma akkor a felulet is megvaltozna?
    Mert eddig csak annyira jutottam, hogyha valtoztatom a collectiont , kapok egy ilyen kivetelt:

    This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
    Mutasd a teljes hozzászólást!
  • Nemnagyon tolonganak az emberek ebben a topicban


    Én spec. még a mai napon is másnapos vagyok, akkora volt a Szilveszter (és környéke). Szívesen segítenék, de kevés az információ. Pontosan milyen WPF felületbe szeretnéd a képlistát megjeleníteni. Van XAML kód? Csak úgy nagyjából kéne, hogy lássam. Elég a terv is.
    Mutasd a teljes hozzászólást!
  • A szilveszter hatasat en is erzem meg! Persze mutatok forraskodot. Most ugy erzem nagyon elvesztem...

    1. reszlet a KivKepek.xaml -bol

    <ItemsControl Name="KivalasztottKepekMegj" DataContext="{DynamicResource Kepek}" ItemsSource="{Binding}" Margin="10,30,0,0"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <local:FanPanel Cursor="Hand" MouseLeftButtonUp="OnClick" Loaded="OnLoaded" AnimationCompleted="OnCompleted" AnimationMilliseconds="2000"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="{x:Type ContentPresenter}"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <Image Source="{Binding}" Width="{Binding ElementName=detailSlider,Path=Value}" Margin="5" MouseLeftButtonUp="OnItemClick"/> </DataTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Panel.ZIndex" Value="2000" /> </Trigger> </Style.Triggers> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl>

    2. Resource-kent viszem be az App.xaml-be:

    <Application.Resources> <CollectionViewSource x:Key="Kepek" Source="{x:Static local:Kep.Path}"/> </Application.Resources>

    3. van egy gyujto osztalyom c#-ban(Kep.cs):

    class Kep { public static ObservableCollection<BitmapImage> Path { get { return new KonyvtarBeolvas().Paths; } } }

    4. es a ObservableCollection-t ideiglenesen megvalosito osztaly:

    class KonyvtarBeolvas : ObservableCollection<BitmapImage> { private string path = @"c:\Kepek"; private string DirPath = String.Empty; public ObservableCollection<BitmapImage> Paths { get { return this; } } public KonyvtarBeolvas() : base() { if (Directory.Exists(path)) { DirPath = path; pathsBeallito(); FileSystemWatcher watcher = new FileSystemWatcher(DirPath); watcher.EnableRaisingEvents = true; watcher.IncludeSubdirectories = true; watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName; watcher.Filter = "*.jpg"; watcher.Changed += new FileSystemEventHandler(Valtozott); watcher.Created += new FileSystemEventHandler(Valtozott); watcher.Deleted += new FileSystemEventHandler(Valtozott); } else throw new DirectoryNotFoundException(); } private void Valtozott(object source, FileSystemEventArgs e) { pathsBeallito(); } private void pathsBeallito() { this.Clear(); foreach (string file in Directory.GetFiles(DirPath)) { if (file.ToLower().EndsWith(".jpg")) { this.Add(new BitmapImage(new Uri(file.ToString()))); } } } }

    Szoval az lenne a lenyeg, hogy a c:\Kepek mappaban levo .jpg kepet jelenitem meg egy kis animacio segitsegevel. Megvalositottam egy FileSystemWatcher-t ami, ha valtozik a konyvtar tartalma, a Collectiont ujra eloallitja. Viszont ebben a pillanatban kapom egy kivetelt...
    Nem vagyok meg valami jartas ebben a temaban, de ugy informalodtam, hogy ez megoldhato... vagy nem ilyen egyszeru?
    Mutasd a teljes hozzászólást!
  • A hibát azért kapod, mert a FileSystemWatcher egy másik thread-ben fut, onnan pittyog a changed event, és a CollectionView nincs felkészítve a multithreadingre. Egyébként az XAML kóddal nincs hiba. Mondjuk a DataContext="{DynamicResource Kepek}" miért nem StaticResource? Ide felesleges dinamikust tenni. Az MSDN Resources Overview pontjában lehet olvasni erről részletesen, ha jól emléxem. Ha nem, akkor Neked van igazad, és dinamikus.

    Az a statikus segédosztály csúnyaság nagyon nem kell ám ide! Minek? Van a collection osztályod. Natúr objektum, be a Resource közé egy kulccsal, aztán e helyett:

    <CollectionViewSource x:Key="Kepek" Source="{x:Static local:Kep.Path}"/>

    ez:

    <CollectionViewSource x:Key="Kepek" Source="{StaticResource Pics}"/>

    Itt egy példa az MSDN-ről, hogy hogyan lehet ezt az ObservableCollection-t beimádkozni WPF alá.

    A következő feladat, hogy a másik thread eseményeit átküld az UI-WPF threadbe, azért, hogy a CollectionView ne b'zeránkosodjon meg tőle. Erre sok módszer van, az a f'sza egyszerű, meg az az a sz'r bonyolult.

    Itt egy példa.

    Innetől már nem lehet probléma ezzel a kis feladattal. Jó munkát!
    Mutasd a teljes hozzászólást!
  • Igen... a StaticResource jogos es a statikus osztalyt is kipnoztam:) Most igy nez ki az App.xaml:

    <Application.Resources> <local:KonyvtarBeolvas x:Key="Kepbeolvasas"/> <CollectionViewSource x:Key="Kepek" Source="{StaticResource Kepbeolvasas}"/> </Application.Resources>

    Viszont nemtudom hogy meghivni az osztalyomat...

    public KonyvtarBeolvas(Dispatcher dispatcher) : base() { .... }

    szoval hogy egy Dispatcher-t is at kellene adni, amit az App.xaml persze nem tesz meg... hogy lehetne megis parameterezni?
    Mutasd a teljes hozzászólást!
  • Egyszerű, szerintem. Kiveszed a konstruktorból a dispatchert és csinálsz helyette egy CollectionViewSource publikus tulajdonságot, és ehhez adod meg az XAML-ben deklarált CollectionViewSource-t, ott. Amikor a tulajdonság értéket kap, akkor tudni fogod, hogy az adott view-nek meg kell adni a "value.Source = this" -t, a Dispathcert pedig meg tudod kérdezni a value-től, hiszen az is a DispatcherObject-ből származik. Az XAML így néz ki:

    <Application.Resources> <CollectionViewSource x:Key="Kepek"/> <local:KonyvtarBeolvas x:Key="Kepbeolvasas" CVS="{StaticResource Kepek}"/> </Application.Resources>

    Most ezt én csak hasra mondom, mert nem próbáltam ki, de működnie kell(ene). Ha nem, akkor szólj!
    Mutasd a teljes hozzászólást!
  • Ez most nem teljesen vilagos...

    csinálsz helyette egy CollectionViewSource publikus tulajdonságot

    Miert?

    az adott view-nek meg kell adni a "value.Source = this" -t, a Dispathcert pedig meg tudod kérdezni a value-től

    Ezt hogyan?

    Bocsi, valoszinu csak felreertettelek, vagy !
    Mutasd a teljes hozzászólást!
  • Szóval, az a lényeg, hogy a CollectionViewSource, amit az XAML-ben deklarálsz, megkapja valahogy a KonyvtarBeolvas objektumot a Source tulajdonságába. Közben a KonyvtarBeolvas objektumnak meg kell a CollectionViewSource Dispatchere, hogy tudjon BeginInvoke-t hívni. XAML-ből pedig csak default konstruktort lehet hívni. Right?

    Ez kell a KonyvtarBeolvas osztályba:

    public class KonyvtarBeolvas ... { ... private CollectionViewSource _cvs; public CollectionViewSource CVS { get { return _cvs; } set { if (_cvs != null) _cvs.Source = null; if ((_cvs = value) != null) _cvs.Source = this; } } protected Dispatcher UIDispatcher { get { return _cvs != null ? _cvs.Dispatcher : null; } } ... }

    Innentől ready, mert a CollectionViewSource.Source is beállítódik, és a Dispatchert is tudja a KonyvtarBeolvas, mehet a BeginInvoke.

    Ez az XAML:

    <Application.Resources> <CollectionViewSource x:Key="Kepek"/> <local:KonyvtarBeolvas x:Key="Kepbeolvasas" CVS="{StaticResource Kepek}"/> </Application.Resources>

    C#-ul annyi, mint:

    CollectionViewSource cvs = new CollectionViewSource(); Application.Resources.Add("Kepek" , cvs); KonyvtarBeolvas kb = new KonyvtarBeolvas(); kb.CVS = cvs; // itt "cvs.Source = kb" is megtortenik Application.Resources.Add("Kepbeolvasas" , cvs);

    Magyarul mindent beállítottunk, amire a működésnek szüksége van. Meg kell tanulni olvasni az XAML sorok között. Az egész semmi más, mint objektumok létrehozása, tulajdonságok inicializációja, és a ResourceDictionary menedzsmentje. Gyakorlatilag az egész C#-ban is leírható és olvasható.
    Mutasd a teljes hozzászólást!
  • Miattam csinálhatod fordítva is:

    public class MyCollectionViewSource : CollectionViewSource { new public object Source { get { return base.Source; } set { KonyvtarBeolvas kb = base.Source as KonyvtarBeolvas; if (kb != null) kb.UIDispatcher = null; kb = ((base.Source = value) as KonyvtarBeolvas; if (kb != null) kb.UIDispatcher = this.Dispatcher; } } } ... public class KonyvtarBeolvas ... { ... private Dispatcher _uiDispatcher; public Dispatcher UIDispatcher { get { return _uiDispatcher; } set { _uiDispatcher = value; } } ... }

    Az XAML pedig így maradhat a Te verziód, annyi különbséggel, hogy:

    <Application.Resources> <local:KonyvtarBeolvas x:Key="Kepbeolvasas"/> <local:MyCollectionViewSource x:Key="Kepek" Source="{StaticResource Kepbeolvasas}"/> </Application.Resources>

    Ezeket most csak így írom ide, nem teszteltem, de működniük kell, hiszen csak sima, natúr objektum-inicializálásról beszélünk.

    Itt már a MyCollectionSource is létrehozhatja a konstruktorában a KonyvtarBeolvas osztályt, ahol már tudsz konstruktort hívni, hiszen ez már a sajátod, azt írsz bele, amit akarsz. Ez a verzió onnantól érdekes, hogy a KonyvtarBeolvas valami saját absztrakt bázis, amiből ha származtatsz a projektekben, akkor minden ebből keletkező osztály thread safe lesz, ha a MyCollectionViewSource-ba kerül. Én mindenesetre így állnék neki, mert erre más projekteknél is szükség lesz, jó ha egy assemblyben megvannak ezek a saját okosságok WPF-hez.
    Mutasd a teljes hozzászólást!
  • Jó, hogy előhoztad! Csinálok is egy ilyen cuccost magamnak, bäzzeg! Jó lesz ez még sok mindenre.
    Mutasd a teljes hozzászólást!
  • A reggel összedobtam. A jó hír az, hogy nem kell sza...szórakozni a Dispatcher átadásával, mert a Dispatcher osztálynak van egy statikus CurrentDispatcher tulajdonsága. Szóval a Te eredeti XAML kódod úgy jó, ahogy van.

    <Application.Resources> <local:KonyvtarBeolvas x:Key="Kepbeolvasas"/> <CollectionViewSource x:Key="Kepek" Source="{StaticResource Kepbeolvasas}"/> </Application.Resources>

    Az OkumlacijosCollection-t pedig így kell megcsinyáni (ezek az én osztályelnevezéseim):

    public class ObservableCollectionMT<T> : ObservableCollection<T> { private Dispatcher dispatcherUIThread; private delegate void SetItemCallback(int index, T item); private delegate void RemoveItemCallback(int index); private delegate void ClearItemsCallback(); private delegate void InsertItemCallback(int index, T item); private delegate void MoveItemCallback(int oldIndex, int newIndex); public ObservableCollectionMT() { this.dispatcherUIThread = Dispatcher.CurrentDispatcher; } protected override void SetItem(int index, T item) { if (dispatcherUIThread.CheckAccess()) { base.SetItem(index, item); } else { dispatcherUIThread.BeginInvoke(DispatcherPriority.Send, new SetItemCallback(SetItem), index, item); } } // Similar code for RemoveItem, ClearItems, InsertItem and MoveItem (...) }

    Ebből thread safe BitmapImage collectiont, ami neked kell, így származtatod (az MT nálam a multi-threadinget jelenti):

    public class BitmapImageCollectionMT : ObservableCollectionMT<BitmapImage> { }

    OK. Van egy thread safe bitmap kolleckciód, ebből már lehet is gyártani a Watchert:

    public class JpgWatcher : BitmapImageCollectionMT { . . . }

    Ebbe nyugodtan teheted a FileSystemWatchert, ami úgy variálja a kollekciót, ahogy akarja, nem fog összeakadni a dolog az UI thread-del.

    Ami még kiderült az az, hogy a PropertyChanged eseményeket a WPF képes szálbiztosan kezelni, ott ezzel nem kell már foglalkozni. Je.
    Mutasd a teljes hozzászólást!
  • Ugy nez ki meg mindig nem tokeletes minden... vagy megint en vagyok a gyenge lancszem...
    Ezt irtam:

    class KonyvtarBeolvas : ObservableCollection<BitmapImage> { private string path = @"c:\Kepek"; private string DirPath = String.Empty; private Dispatcher uiDispatcher; private delegate void ClearItemsCallback(); private delegate void InsertItemCallback(int index, BitmapImage item); public Dispatcher UIDispatcher { get { return uiDispatcher; } set { uiDispatcher = value; } } public ObservableCollection<BitmapImage> Paths { get { return this; } } public KonyvtarBeolvas() : base() { this.uiDispatcher = Dispatcher.CurrentDispatcher; if (Directory.Exists(path)) { DirPath = path; pathsBeallito(); FileSystemWatcher watcher = new FileSystemWatcher(DirPath); watcher.EnableRaisingEvents = true; watcher.IncludeSubdirectories = true; watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName; watcher.Filter = "*.jpg"; watcher.Changed += new FileSystemEventHandler(Valtozott); watcher.Created += new FileSystemEventHandler(Valtozott); watcher.Deleted += new FileSystemEventHandler(Valtozott); } else throw new DirectoryNotFoundException(); } private void Valtozott(object source, FileSystemEventArgs e) { pathsBeallito(); } protected override void ClearItems() { if (uiDispatcher.CheckAccess()) { base.ClearItems(); } else { uiDispatcher.BeginInvoke(DispatcherPriority.Send, new ClearItemsCallback(ClearItems)); } } protected override void InsertItem(int index, BitmapImage item) { if (uiDispatcher.CheckAccess()) { base.InsertItem(index, item); } else { uiDispatcher.BeginInvoke(DispatcherPriority.Send, new InsertItemCallback(InsertItem), index, item ); } } private void pathsBeallito() { ClearItems(); foreach (string file in Directory.GetFiles(DirPath)) { if (file.ToLower().EndsWith(".jpg")) { InsertItem(this.Count, new BitmapImage(new Uri(file.ToString()))); } } } }

    Elso futasra siman megy, de amint megvaltoztatom a konyvtarat.. egy masik osztalyom ami a kepek rajzolasaval torodik (pontosabban a merettel) itt a reszlet:


    protected override Size MeasureOverride(Size availableSize) { Size size = new Size(Double.PositiveInfinity, Double.PositiveInfinity); foreach (UIElement child in Children) { child.Measure(size); } if (double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width)) return new Size(600, 600); else return availableSize; }


    Exception has been thrown by the target of an invocation.
    System.Reflection.TargetInvocationException was unhandled.

    Kivetellel megszakad a futasom a child.Measure(size); sorban.
    Mutasd a teljes hozzászólást!
  • Ja igen. Azt elfelejtettem mondani, hogy ne azt csináld, hogy a kollekciód BitmapImage-ket tartalmaz, mert ezeket az objektumokat egy másik threadben hozod így létre, ami bajosan fog együttműködni a WPF-fel. Pl. legyen egy sima string collection, ami a képek útvonalát tartalmazza, és csinálsz egy sablont, ami tartamlazza az Image-t és a BitmapImage-t, és a BitmapImage uri tulajdonságába kötöd be az útvonalat.
    Mutasd a teljes hozzászólást!
  • Koszonom.. sikerult megoldanom! Jar a pont. szerintem tobb is mint 50, de asszem ezt en nem hatarozhatom meg...
    Mar csak egy problemaval kell szembeneznem. Ha egyszerre pl. 2db kepet masolok be a konyvtarba, akkor a Collection-t 2x probalja meg frissiteni. Elso ranezesre szerintem valami ilyesmi megteszi, de bizonyara akad ennel eletrevalobb megoldas is:

    private void pathsBeallito() { System.Threading.Thread.Sleep(1000); foreach (string file in Directory.GetFiles(DirPath)) { if (file.ToLower().EndsWith(".jpg")) { String temp=new String(file.ToString().ToCharArray()); if(!this.Contains(temp)) InsertItem(this.Count, temp); } } }

    Megeszer koszi a kozremukodest!
    Mutasd a teljes hozzászólást!
  • A FileSystemWatcher képes külön szólni minden változásról, nem kell ezért mindig újraépíteni a listát. Ha új file, akkor hozzáteszed, ha törlés, akkor elveszed, ha átnevezés, akkor kicsréled.
    Mutasd a teljes hozzászólást!
  • Igen ez rendben van. De 2 file bemasolasakor (egymas utan rogtron, ugymond majdnem egy idoben), 2x szol! Es mire elraknam az elsot, mar szol is a masodik file miatt, es ez elosot 2x-is elrakom, emiatt kerult be sleep. Jobb hirtelen nem jutott eszembe
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd