2009-02-27 23 views
6

, ben sürekli çalıştı gereken yarış durumu bir çeşit karşı gelmek gibi görünüyor. En yenisi aşağıda. Herhangi bir yardım takdir edilecektir. biri Bileşenler ve bir Üreticileri geçerli:Silverlight Combobox Veri bağlama yarış durumu

Geri ucunda iki tablo var. Her Bileşen BİR Üretici var. Hiç sıradışı, yabancı anahtar arama-ilişki değil.

Silverlight, WCF servisi aracılığıyla verilere erişmek. Bir ComboBox için olası seçimleri doldurmak için üreticilerin tam listesini almak üzere Geçerli bileşeni (görüntülemek veya düzenlemek için) ve Produ__GetAll() öğesine bir çağrı almak için Components_Get (id) öğesine çağrı yapacağım. Daha sonra ComboBox'taki SelectedItem'i Geçerli Bileşen için Üreticiye ve ComboBox'taki ItemSource'a olası Üretici listesine bağlarım. Böyle: Ben zeki var ve (Ben de Üreticileri için açmak planlanan) Bileşen küçük istemci tarafında önbelleğe alma yapana kadar

<UserControl.Resources> 
    <data:WebServiceDataManager x:Key="WebService" /> 
</UserControl.Resources> 
<Grid DataContext={Binding Components.Current, mode=OneWay, Source={StaticResource WebService}}> 
    <ComboBox Grid.Row="2" Grid.Column="2" Style="{StaticResource ComboBoxStyle}" Margin="3" 
       ItemsSource="{Binding Manufacturers.All, Mode=OneWay, Source={StaticResource WebService}}" 
       SelectedItem="{Binding Manufacturer, Mode=TwoWay}" > 
     <ComboBox.ItemTemplate> 
      <DataTemplate> 
       <Grid> 
        <TextBlock Text="{Binding Name}" Style="{StaticResource DefaultTextStyle}"/> 
       </Grid> 
      </DataTemplate> 
     </ComboBox.ItemTemplate> 
    </ComboBox> 
</Grid> 

Bu, uzun süre büyük çalıştı. Bileşen için önbelleğe alma özelliğini açtığımda ve bir önbellek vurulduğumda, tüm veriler doğru şekilde nesnelerde olabilirdi, ancak SelectedItem Bağlanamazdı. Bunun nedeni, çağrıların Silverlight'ta Asenkron olarak ayarlanması ve önbelleğe alma işleminin faydası olması nedeniyle, Bileşen üreticiden önce iade edilmemesidir. Böylece SelectedItem ItemsSource listesinde Components.Current.Manufacturer bulmaya çalıştığında, orada değil, çünkü bu liste hala boş çünkü Manufacturers.All henüz WCF hizmetinden yüklenmemiş. Yine, Bileşen önbelleklemeyi kapatırsam, yine çalışır, ancak YANLIŞ hisseder - tıpkı zamanlamanın çalıştığı için şanslı olduğum gibi. Doğru düzeltme IMHO, MS'in Asynch çağrılarının normal olmasıyla gerçekleşeceğini anlamak için ComboBox/ItemsControl denetimini düzeltmek içindir.

  1. önbelleğe eleyin veya bir kez daha maskelemek için yönüyle açın: Ama o zamana kadar ben bir ihtiyaç yo bunu düzeltmek bir yolu ... İşte

    ben düşündüm bazı seçenekler şunlardır gerek sorun. İyi IMHO değil, çünkü bu tekrar başarısız olur. Halı altına geri süpürmeye pek istekli değil.
  2. Eşitlemeyi benim için yapacak olan bir aracı nesne oluşturun (ItemsControl'ün kendisinde yapılması gerekir). Her ikisi de geldiğinde, Öğeyi ve bir ItemsList öğesini ve çıktıyı ve ItemWithItemsList özelliğini kabul eder. ComboBox'ı sonuçta elde edilen çıktıya bağlardım, böylece bir öncekinden asla bir ürün almazdı. Benim problemim bunun bir acı gibi gözükmesi ama yarış koşullarının yeniden oluşmamasından emin olmak.

Herhangi thougnts/Yorumlar?

FWIW: Ben başkalarının yararına burada çözüm yayınlayacağız.

@Joe: yanıt için çok teşekkür ederiz. UI'yi yalnızca UI iş parçacığından güncelleme ihtiyacının farkındayım. Anladığım kadarıyla, bunu SL2'de Service Reference tarafından oluşturulan kodun sizin için halledeceğini hata ayıklayıcı aracılığıyla doğruladım. yani, Manufacturers_GetAll_Asynch() yöntemini çağırdığımda, sonuçların Manufacturers_GetAll_Completed olayı aracılığıyla elde edilmesini sağlayacağım. Oluşturulan Hizmet Referans kodunun içine bakarsanız, * Tamamlanmış olay işleyicisinin UI iş parçacığından çağrılmasını sağlar. Benim sorunum bu değil, iki farklı arama (üretici listesi için ve bir üreticinin kimliğine başvuran bileşen için bir tane) yaptıktan sonra, bu sonuçların her birini tek bir ComboBox'a bağladım. Her ikisi de UI iş parçacığına bağlanırlar, sorun, listeden önce listeye ulaşmazsa seçimin yok sayılmasıdır.Ayrıca bu sorunun hala if you just set the ItemSource and the SelectedItem in the wrong order olduğunu unutmayın!

Başka Bir Güncelleme: Hala combobox yarış koşulu varken, ilginç başka bir şey keşfettim. ASLA, bu özellik için "getter" içinden bir PropertyChanged olayı oluşturmalısınız. Örnek: "SL" veri neslimdeki "Üretici" türünde "Tümü" adlı bir özelliğim var. Bu kod bir "önbellek isabet" konulu GETİRMEDİĞİ

public class ManufacturersData : DataServiceAccessbase 
{ 
    public ObservableCollection<Web.Manufacturer> All 
    { 
     get 
     { 
      if (!AllLoaded) 
       LoadAllManufacturersAsync(); 
      return mAll; 
     } 
     private set 
     { 
      mAll = value; 
      OnPropertyChanged("All"); 
     } 
    } 

    private void LoadAllManufacturersAsync() 
    { 
     if (!mCurrentlyLoadingAll) 
     { 
      mCurrentlyLoadingAll = true; 

      // check to see if this component is loaded in local Isolated Storage, if not get it from the webservice 
      ObservableCollection<Web.Manufacturer> all = IsoStorageManager.GetDataTransferObjectFromCache<ObservableCollection<Web.Manufacturer>>(mAllManufacturersIsoStoreFilename); 
      if (null != all) 
      { 
       UpdateAll(all); 
       mCurrentlyLoadingAll = false; 
      } 
      else 
      { 
       Web.SystemBuilderClient sbc = GetSystemBuilderClient(); 
       sbc.Manufacturers_GetAllCompleted += new EventHandler<hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs>(sbc_Manufacturers_GetAllCompleted); 
       sbc.Manufacturers_GetAllAsync(); ; 
      } 
     } 
    } 
    private void UpdateAll(ObservableCollection<Web.Manufacturer> all) 
    { 
     All = all; 
     AllLoaded = true; 
    } 
    private void sbc_Manufacturers_GetAllCompleted(object sender, hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs e) 
    { 
     if (e.Error == null) 
     { 
      UpdateAll(e.Result.Records); 
      IsoStorageManager.CacheDataTransferObject<ObservableCollection<Web.Manufacturer>>(e.Result.Records, mAllManufacturersIsoStoreFilename); 
     } 
     else 
      OnWebServiceError(e.Error); 
     mCurrentlyLoadingAll = false; 
    } 

} 

Not olduğunu bir PropertyChanged olay üretecektir çünkü: Get In {} o yüklendikten eğer bunu böyle yükler değilse, bakar All {Get {}} yönteminden, normalde Binding System'in All {get {}} öğesini tekrar çağırmasına neden olacak olan yöntem için ... Bir ScottGu blogu geri gönderme yolundan bindable silverlight veri nesnelerini oluşturma modelini kopyaladım ve bana genel olarak iyi hizmet etti, ama bunun gibi şeyler oldukça zorlaştırıyor. Neyse ki düzeltme basittir. Umarım bu bir başkasına yardım eder.

cevap

7

Tamam Cevabı buldum (ComboBox'ın nasıl çalıştığını anlamak için birçok Reflektör kullanarak).

SelectedItem ayarlandıktan sonra ItemSource ayarlandığında sorun var. Bu olduğunda Combobx, seçimi tam bir Reset olarak görür ve SelectedItem/SelectedIndex'i temizler. Yeni bir ItemSource sen sona yüklediğinizde ... sıfırlama -

protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
{ 
    base.OnItemsChanged(e); 
    int selectedIndex = this.SelectedIndex; 
    bool flag = this.IsInit && this._initializingData.IsIndexSet; 
    switch (e.Action) 
    { 
     case NotifyCollectionChangedAction.Add: 
      if (!this.AddedWithSelectionSet(e.NewStartingIndex, e.NewStartingIndex + e.NewItems.Count)) 
      { 
       if ((e.NewStartingIndex <= selectedIndex) && !flag) 
       { 
        this._processingSelectionPropertyChange = true; 
        this.SelectedIndex += e.NewItems.Count; 
        this._processingSelectionPropertyChange = false; 
       } 
       if (e.NewStartingIndex > this._focusedIndex) 
       { 
        return; 
       } 
       this.SetFocusedItem(this._focusedIndex + e.NewItems.Count, false); 
      } 
      return; 

     case NotifyCollectionChangedAction.Remove: 
      if (((e.OldStartingIndex > selectedIndex) || (selectedIndex >= (e.OldStartingIndex + e.OldItems.Count))) && (e.OldStartingIndex < selectedIndex)) 
      { 
       this._processingSelectionPropertyChange = true; 
       this.SelectedIndex -= e.OldItems.Count; 
       this._processingSelectionPropertyChange = false; 
      } 
      if ((e.OldStartingIndex <= this._focusedIndex) && (this._focusedIndex < (e.OldStartingIndex + e.OldItems.Count))) 
      { 
       this.SetFocusedItem(-1, false); 
       return; 
      } 
      if (e.OldStartingIndex < selectedIndex) 
      { 
       this.SetFocusedItem(this._focusedIndex - e.OldItems.Count, false); 
      } 
      return; 

     case NotifyCollectionChangedAction.Replace: 
      if (!this.AddedWithSelectionSet(e.NewStartingIndex, e.NewStartingIndex + e.NewItems.Count)) 
      { 
       if ((e.OldStartingIndex <= selectedIndex) && (selectedIndex < (e.OldStartingIndex + e.OldItems.Count))) 
       { 
        this.SelectedIndex = -1; 
       } 
       if ((e.OldStartingIndex > this._focusedIndex) || (this._focusedIndex >= (e.OldStartingIndex + e.OldItems.Count))) 
       { 
        return; 
       } 
       this.SetFocusedItem(-1, false); 
      } 
      return; 

     case NotifyCollectionChangedAction.Reset: 
      if (!this.AddedWithSelectionSet(0, base.Items.Count) && !flag) 
      { 
       this.SelectedIndex = -1; 
       this.SetFocusedItem(-1, false); 
      } 
      return; 
    } 
    throw new InvalidOperationException(); 
} 

Not son durum: System.Windows.Controls.Primitives.Selector burada (ComboBox için temel sınıf) bunu görebilirsiniz yukarı ve herhangi bir SelectedItem/SelectedIndex patladı mı?!?! Sonunda çözüm oldukça basitdi. Sadece hatalı ComboBox alt sınıfı ve aşağıdaki gibi bu yöntem için sağlanan ve geçersiz kıldım. Ben eklemek zorunda mı rağmen bir:

Hepsi
public class FixedComboBox : ComboBox 
{ 
    public FixedComboBox() 
     : base() 
    { 
     // This is here to sync the dep properties (OnSelectedItemChanged is private is the base class - thanks M$) 
     base.SelectionChanged += (s, e) => { FixedSelectedItem = SelectedItem; }; 
    } 

    // need to add a safe dependency property here to bind to - this will store off the "requested selectedItem" 
    // this whole this is a kludgy wrapper because the OnSelectedItemChanged is private in the base class 
    public readonly static DependencyProperty FixedSelectedItemProperty = DependencyProperty.Register("FixedSelectedItem", typeof(object), typeof(FixedComboBox), new PropertyMetadata(null, new PropertyChangedCallback(FixedSelectedItemPropertyChanged))); 
    private static void FixedSelectedItemPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
    { 
     FixedComboBox fcb = obj as FixedComboBox; 
     fcb.mLastSelection = e.NewValue; 
     fcb.SelectedItem = e.NewValue; 
    } 
    public object FixedSelectedItem 
    { 
     get { return GetValue(FixedSelectedItemProperty); } 
     set { SetValue(FixedSelectedItemProperty, value);} 
    } 
    protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
    { 
     base.OnItemsChanged(e); 
     if (-1 == SelectedIndex) 
     { 
      // if after the base class is called, there is no selection, try 
      if (null != mLastSelection && Items.Contains(mLastSelection)) 
       SelectedItem = mLastSelection; 
     } 
    } 

    protected object mLastSelection = null; 
} 

bu, (a) ardından eski SelectedItem ve kapatma tasarruf edilir yaptığı, (b) kontrol etmenizi eğer ItemsChanged sonra, yaptığımız hiçbir seçimi ve eski SelectedItem varsa Yeni listede var ... iyi ... Seçildi!

+0

Bu çözüm, yaygın bir çözümdür. Uzun zamandır, tüm Selector kontrollerini kapsayan daha jenerik bir çözüm arıyordum; sadece ComboBoxes değil, ve herhangi bir kontrolden miras kalmadan yapar. Bunu davranışlarla yapmanın bir yolu var. Bu önerilen çözüm UWP'de ve muhtemelen WPF'de de çalışıyor: http://stackoverflow.com/questions/36003805/uwp-silverlight-combobox-selector-itemssource-selecteditem-race-condition-solu –

0

UI iş parçacığındaki kullanıcı arabirimi öğelerini değiştirmeniz gerektiğinin farkında olup olmadığına bakılmaksızın, iletinizden net değil - ya da sorunlarınız olacak. İşte şimdiki zaman ile bir TextBox değiştiren bir arka plan iş parçacığı oluşturan kısa bir örnek.

Anahtar, Page.xaml.cs dosyasındaki MyTextBox.Dispather.BeginInvoke'dür.

Page.xaml:

<UserControl x:Class="App.Page" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300" 
      Loaded="UserControl_Loaded"> 
    <Grid x:Name="LayoutRoot"> 
     <TextBox FontSize="36" Text="Just getting started." x:Name="MyTextBox"> 
     </TextBox> 
    </Grid> 
</UserControl> 

Page.xaml.cs: zaman uyumsuz şeyler almak istiyorsanız

using System; 
using System.Windows; 
using System.Windows.Controls; 

namespace App 
{ 
    public partial class Page : UserControl 
    { 
     public Page() 
     { 
      InitializeComponent(); 
     } 

     private void UserControl_Loaded(object sender, RoutedEventArgs e) 
     { 
      // Create our own thread because it runs forever. 
      new System.Threading.Thread(new System.Threading.ThreadStart(RunForever)).Start(); 
     } 

     void RunForever() 
     { 
      System.Random rand = new Random(); 
      while (true) 
      { 
       // We want to get the text on the background thread. The idea 
       // is to do as much work as possible on the background thread 
       // so that we do as little work as possible on the UI thread. 
       // Obviously this matters more for accessing a web service or 
       // database or doing complex computations - we do this to make 
       // the point. 
       var now = System.DateTime.Now; 
       string text = string.Format("{0}.{1}.{2}.{3}", now.Hour, now.Minute, now.Second, now.Millisecond); 

       // We must dispatch this work to the UI thread. If we try to 
       // set MyTextBox.Text from this background thread, an exception 
       // will be thrown. 
       MyTextBox.Dispatcher.BeginInvoke(delegate() 
       { 
        // This code is executed asynchronously on the 
        // Silverlight UI Thread. 
        MyTextBox.Text = text; 
       }); 
       // 
       // This code is running on the background thread. If we executed this 
       // code on the UI thread, the UI would be unresponsive. 
       // 
       // Sleep between 0 and 2500 millisends. 
       System.Threading.Thread.Sleep(rand.Next(2500)); 
      } 
     } 
    } 
} 

Yani, bildirmek için Control.Dispatcher.BeginInvoke kullanmak zorunda olacak UI öğesi, yeni verileriniz var.

+0

); durumda kaza

ben hem hataları yapılmış tarafından silin. Bu konudaki orijinal soruda beni düzenle'yi gör (burada sınırlı bir alan var ve oraya yerleştirmeyi gerektirdi). – caryden

0

ItemsSource öğesinin her defasında bir ObservableCollection <> öğesine bağlamanız daha kolay olurdu ve daha sonra Clear() üzerine tıklayın ve Add (...) öğelerini ekleyin. Bu şekilde bağlanma sıfırlanmaz.

Başka bir yakalama, seçilen öğenin listedeki nesnelerin bir örneği olması ZORUNLUDUR. Varsayılan öğe için sorgulanan listenin düzeltildiğini düşündüğümde ancak her çağrıda yeniden oluşturulduğunda bir hata yaptım. Böylece akım, listenin bir öğesiyle aynı olan bir DisplayPath özelliğine sahip olmasına rağmen farklıydı.

Geçerli öğenin kimliğini (veya onu benzersiz olarak tanımlayan her şeyi) hala alabilirsiniz, denetimi yeniden adlandırın ve sonra aynı kimlikle birlikte ciltlenen öğeyi bulup bu öğeyi geçerli olarak bağlayabilirsiniz.

1

Basamaklı comboboxlar oluştururken aynı sorunla uğraştım ve kolay ama şaşırtıcı bir çözüm bulan birinin blog gönderisine rastladım. .ItemsSource ayarladıktan sonra SelectedItem'i ayarlamadan önce UpdateLayout() öğesini çağırın. Bu, kodlama tamamlanana kadar kodun engellenmesini zorlamalıdır. Ben ... Bu bilgi

Kaynak bunu düzeltir ama beri tekrar yarış durumu yaşadığınız değil neden tam emin değilim: http://compiledexperience.com/Blog/post/Gotcha-when-databinding-a-ComboBox-in-Silverlight.aspx Önce bu sorun koştu zaman öfkeli oldu

2

ama Etrafında bir yol olması gerektiğini düşündüm. Şimdiye kadar yaptığım en iyi çaba, postada ayrıntılı olarak açıklandı. o Aşağıdaki gibi bir şey söz dizimi daralmış olarak

http://blogs.msdn.com/b/kylemc/archive/2010/06/18/combobox-sample-for-ria-services.aspx

oldukça mutlu oldu. Eğer bir Combobox seçim problemi, anlama sahip çünkü

<ComboBox Name="AComboBox" 
     ItemsSource="{Binding Data, ElementName=ASource}" 
     SelectedItem="{Binding A, Mode=TwoWay}" 
     ex:ComboBox.Mode="Async" /> 

Kyle durumda

+0

Teşekkürler Kyle. Şimdiye kadar denedim tüm ex: ComboBox.Mode = "AsyncEager" ancak SelectedItem öğesinin burada açıklanan sorunların bir çoğunun çekirdeği gibi görünen ItemsSource önce ayarlanması gereken kısıtlama kaldırıldı. Silverlight 5'te gelen yerli bir çözüm olup olmayacağını biliyor musun? –

+0

SL5'te ne olacağını bilmiyorum ama bu satırlarda hiçbir şey duymadım. –

0

Eğer listede öğenin üzerine tıkladığınızda hiçbir şey olmuyor, burada varıyoruz. Aşağıdaki ipuçları da size yardımcı olabileceğini unutmayın:

1/Bir öğeyi seçmek için bu durumda bir şey bildirmeyen emin olun

public string SelectedItem 
     { 
      get 
      { 
       return this.selectedItem; 
      } 
      set 
      { 
       if (this.selectedItem != value) 
       { 
        this.selectedItem = value; 
        //this.OnPropertyChanged("SelectedItem"); 
       } 
      } 
     } 

2/emin seçmek öğe altta yatan veri kaynağı hala yapmak Ben UI Thread gelen UI güncellemek için ihtiyaç farkındayım

İlgili konular