2012-04-23 13 views
5

EDIT2: Quando la tabella viene popolata non riesco più a modificare i valori. Anche quando cambio i valori nell'elenco (da cui gli ItemControls ottengono i valori) il grafico non sembra aggiornarsi con i nuovi valori.Databinding ItemsControl (DataTemplate) non si aggiorna/riceve solo i valori all'avvio del programma

Invito al metodo GetDataGrafiek() nel timer per aggiornare il mio grafico ogni x secondi.

 Grafiek graf = new Grafiek(); 
     graf.GetDataGrafiek(); 

Questo è dovuto il Timer Threading in esecuzione in un thread separato (IsAsynchronous metodo nel ObjectDataProvider) oppure ho bisogno di accedere al DataContext del ItemsControl nel mio metodo timer?

EDIT: ero in grado in grado di popolare il grafico quando il programma era già in esecuzione così ho fatto il ObservableCollection<GrafiekBar> statico (lista che contiene il riempimento e il valore delle barre) e inizializzata è la seguente:

public static ObservableCollection<GrafiekBar> listGrafiek = new ObservableCollection<GrafiekBar>() 
     { 
      new GrafiekBar() {Value = 0, Fill = (Brush)convertor.ConvertFromString(kleuren[0])}, 
      new GrafiekBar() {Value = 0, Fill = (Brush)convertor.ConvertFromString(kleuren[1])}, 
      new GrafiekBar() {Value = 0, Fill = (Brush)convertor.ConvertFromString(kleuren[2])} 
     }; 

Da MSDN: ObjectDataProvider: "Tuttavia, se si esegue il binding a un oggetto che è già stato creato, è necessario impostare DataContext nel codice, come nell'esempio seguente."


Ho un ItemsControl che viene visualizzato come un semplice grafico a barre. Quando assegno i valori (hardcoded nel mio codiceBehind) il grafico viene popolato correttamente.

Quello che faccio è in pratica ottenere il valore più grande impostandolo su 100% e calcolare la lunghezza del resto delle barre.

Problema: Non voglio i valori della barra del grafico codificati, ma le barre devono cambiare runtime. Per questo io uso un Threading.Timer che viene eseguito ogni secondo finché il mio programma è in esecuzione (altri calcoli si verificano anche in questo timer).

I valori della barra del grafico vengono aggiornati in base ai calcoli che avvengono all'interno di questo timer ogni x secondi.

Ho provato tutto e non riesco a ottenere i valori visualizzati quando il mio programma è in esecuzione. Posso vedere solo le barre quando le hardcode (vedi regione GetDataGrafiek() alla fine del thread). Cosa sto facendo di sbagliato/mancante?

Il GetDataGrafiek() (calcoli per popolare il grafico) viene chiamato in uno ObjectDataProvider.

Questo metodo richiede TimeSpan come input e quindi esegue i calcoli in modo da ottenere un valore doppio (basato sul valore 100% spiegato sopra) che viene quindi inserito nel valore della barra (= larghezza della dataTemplate).

<ObjectDataProvider x:Key="odpLbGrafiek" ObjectType="{x:Type myClasses:GrafiekBar}" MethodName="GetDataGrafiek"/> 

DataTemplate per la mia ItemsControl (questo utilizza il valore per il valore delle barre di mia tabella)

<DataTemplate x:Key="GrafiekItemTemplate"> 
     <Border Width="Auto" Height="Auto"> 
      <Grid> 
       <Rectangle StrokeThickness="0" Height="30" 
          Margin="15" 
          HorizontalAlignment="Right" 
          VerticalAlignment="Bottom" 
          Width="{Binding Value}" 
          Fill="{Binding Fill}"> 
        <Rectangle.LayoutTransform> 
         <ScaleTransform ScaleX="20" /> 
        </Rectangle.LayoutTransform> 
       </Rectangle> 
      </Grid> 
     </Border> 
    </DataTemplate> 

ItemsControl:

<ItemsControl x:Name="icGrafiek" 
      Margin="50,3,0,0" 
      ItemsSource="{Binding Source={StaticResource odpLbGrafiek}}" 
      ItemTemplate="{DynamicResource GrafiekItemTemplate}" 
      RenderTransformOrigin="1,0.5" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.RowSpan="6"> 
      <ItemsControl.RenderTransform> 
       <TransformGroup> 
        <ScaleTransform ScaleY="-1" ScaleX="1"/> 
        <SkewTransform AngleY="0" AngleX="0"/> 
        <RotateTransform Angle="180"/> 
        <TranslateTransform/> 
       </TransformGroup> 
      </ItemsControl.RenderTransform> 
     </ItemsControl> 

GetDataGrafiek() La regione mantiene i valori codificati, quando ciò viene eseguito il mio grafico mostra 6 barre con successo. Quando commento la regione non ho più barre visibili.

Questo metodo restituisce un elenco con i valori Double. Ogni valore rappresenta una barra che viene rappresentata come Larghezza nello DataTemplate e il Riempimento gli conferisce solo un determinato colore.

ObservableCollection<GrafiekBar> listGrafiek = new ObservableCollection<GrafiekBar>(); 

    public ObservableCollection<GrafiekBar> GetDataGrafiek() 
    { 
     var converter = new System.Windows.Media.BrushConverter(); 

     #region ***TEST HARDCODED BAR VALUES*** 
      int[] testWaardenUren = new int[] { 2, 1, 0, 1, 2, 0 }; 
      int[] testWaardenMinuten = new int[] { 58, 2, 55, 55, 2, 20 }; 

      for (int j = 0; j < 6; j++) 
      { 
       TimeSpan ts = new TimeSpan(testWaardenUren[j], testWaardenMinuten[j], 0); 
       GlobalObservableCol.regStilstanden[j].Value = ts; 
       GlobalObservableCol.regStilstanden[j].Name = ""; 
      } 
     #endregion 

     totalMinutesMaxValue = GetLargestValueStilstanden(); //= "100%" value 

     //calculate % of stilstanden Values 
     for (int i = 0; i < GlobalObservableCol.regStilstanden.Count; i++) 
     { 
      Double totalMin = GlobalObservableCol.regStilstanden[i].Value.TotalMinutes; 
      totalMin = totalMin/totalMinutesMaxValue * 10; 
      valuesChartPercentage.Add(totalMin); 
     } 

     //the barChart (itemsControl) gets its final values here 
     for (int j = 0; j < GlobalObservableCol.regStilstanden.Count; j++) 
     { 
      GrafiekBar bar = new GrafiekBar(); 
      bar.Value = valuesChartPercentage[j]; 
      bar.Fill = converter.ConvertFromString(kleuren[j]) as Brush; 
      listGrafiek.Add(bar); 
     } 

     return listGrafiek; 
    } 

GrafiekBar.cls

public class GrafiekBar : INotifyPropertyChanged 
{ 
    private double value; 
    private Brush fill; 

    public GrafiekBar() 
    {  
    } 

    public double Value 
    { 
     get { return this.value; } 

     set 
    { 
      this.value = value; 
      NotifyPropertyChanged("Value"); 
     } 
    } 

    public Brush Fill 
    { 
     get { return this.fill; } 

     set 
     { 
      this.fill = value; 
      NotifyPropertyChanged("Fill"); 
     } 
    } 

    //interface INotifyPropertyChanged 
    public event PropertyChangedEventHandler PropertyChanged; 

    //interface INotifyPropertyChanged 
    private void NotifyPropertyChanged(String info) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs(info)); 
     } 
    } 
} 

il timer Threading che passa ogni secondo (che ha la logica di calcolo e la getDataGrafiek() chiamato ci fa un invocare al thread GUI per gli aggiornamenti.

private void MyTimerCallBack(object state) 
    { 
     DisplayWegingInfo(); 

     App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, 
      new Action(() => 
      { 
       //actions that involve updating the UI 
       CaculateTimeBetweenWegingen(); 
      })); 
    } 

migliori saluti Peter.

+0

Non sono sicuro di seguire quello che stai facendo qui, ma avere un timer che corre sembra super strano. Non puoi rispondere agli eventi, invece? – Oliver

+0

Il grafico deve cambiare (o non cambiare se non richiesto) in base a calcoli che si verificano ogni secondo in quel timer. Non sono sicuro di come risolvere questo problema. – PeterP

+0

Solo un'ipotesi approssimativa: hai provato a usare DispatcherTimer invece di un normale Timer. Questo timer è stato ottimizzato per il dispacciamento WPF – SvenG

risposta

0

È uno sparo nel buio, perché non ho letto tutto il codice, ma ecco un pensiero: sei vincolante per una risorsa statica, giusto? Viene letto una volta e questo è tutto, eh? Prova invece DynamicResource.

+0

Ah, non importa, era un'idea stupida. Cosa fa esattamente CaculateTimeBetweenWegingen(); fare? –

0

Se si accede al seguente codice da un altro thread, questo causerà un problema:

private void NotifyPropertyChanged(String info) 
{ 
    PropertyChangedEventHandler handler = PropertyChanged; 
    if (PropertyChanged != null) 
    { 
     PropertyChanged(this, new PropertyChangedEventArgs(info)); 
    } 
} 

Come l'altro thread solleverà un evento che farà sì che il filo Dispatcher (se è sottoscritto l'evento) all'errore

Prova invece

private void NotifyPropertyChanged(String info) 
{ 
    PropertyChangedEventHandler handler = PropertyChanged; 
    if (PropertyChanged != null) 
    { 
     if (System.Threading.Thread.CurrentThread == System.Windows.Application.Current.Dispatcher.Thread) 
      PropertyChanged(this, new PropertyChangedEventArgs(info)); 
     else 
      System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => PropertyChanged(this,new PropertyChangedEventArgs(info)); 
    } 
} 

Inoltre, assicurarsi che si sta utilizzando UpdateSourceMethod = PropertyChanged in WPF.

+0

Inoltre, una cosa che non ho davvero capito è qual è lo scopo del gestore. Se si esegue l'associazione direttamente da WPF, non è necessario il gestore, solo UpdateSourceMethod = PropertyChanged nel binding dei dati in XAML. – William