MVVM WPF DataGrid dziwny przypadek parametru Visibility

0

Dzień dobry,

Mam w aplikacji (MVVM) DataGrid z dość dużą ilością rekordów (około 2600). Rekord posiada zbindowany parametr 'Visibility', który warunkuje jego widoczność. Jeżeli szukana fraza (wpisywana do TextBoksa) znajduje się w kolekcji to parametr ma wartość 'Visible', a jeżeli nie to 'Collapsed'. Dzięki temu realizuję wyszukiwanie w takim DataGridzie.

Teraz o co chodzi... Rekordy po włączeniu okna ładują się bardzo szybko i ładnie się przewijają. DataGrid jest zwirtualizowany i posiada parametr 'ScrollViewer.CanContentScroll' ustawiony na True.

Oto kilka pierwszych rekodrów załadowanej w całości kolekcji:

a8cf58f921.png

A teraz coś czego nie umiem naprawić. Kiedy wpiszę frazę wyszukiwania i program skończy szukać pojawia się taki oto efekt:

dcf99f537b.png

Ponad to scrollowanie pionowe chodzi wtedy niesamowicie wolno.

Tutaj jest kod formatek ze screenów:

<TabControl Grid.Row="3" Grid.Column="0" Margin="0,0,5,0">
      <TabItem>
        <TabItem.Header>
          <Label Content="Dodaj towar:" Padding="0" FontWeight="Bold"/>
        </TabItem.Header>
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="auto"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="auto"/>
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
          </Grid.RowDefinitions>
          <Label Content="Nazwa:" Grid.Row="0" Grid.Column="0"
                 VerticalContentAlignment="Center" Margin="2"/>
          <TextBox Grid.Row="0" Grid.Column="1" Margin="2"
                   VerticalContentAlignment="Center"
                   Text="{Binding DTM_dodawanyTowar,UpdateSourceTrigger=PropertyChanged}">
            <TextBox.InputBindings>
              <KeyBinding Key="Enter" Command="{Binding znajdzTowar}"/>
            </TextBox.InputBindings>
          </TextBox>
          <Button Grid.Row="0" Grid.Column="2" Margin="2"
                  Content="Dodaj" Width="60"
                  Command="{Binding dodajTowar}"/>
        </Grid>
      </TabItem>
    </TabControl>
    <TabControl Grid.Row="4" Grid.Column="0" Margin="0,5,5,0">
      <TabItem>
        <TabItem.Header>
          <Label Content="Lista towarów:" Padding="0" FontWeight="Bold"/>
        </TabItem.Header>
        <DataGrid VerticalGridLinesBrush="Silver" HorizontalGridLinesBrush="Silver" 
                  AutoGenerateColumns="False" HeadersVisibility="Column"
                  CanUserAddRows="False"
                  ItemsSource="{Binding OCTM_listaTowarow,UpdateSourceTrigger=PropertyChanged}"
                  SelectedItem="{Binding TM_wybranyTowar,UpdateSourceTrigger=PropertyChanged}"
                  ScrollViewer.CanContentScroll="True"
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                  SelectionMode="Single"
                  EnableRowVirtualization="True"
                  EnableColumnVirtualization="True">



          <!-- Styl nagłówka kolumn -->
          <DataGrid.ColumnHeaderStyle>
            <Style TargetType="{x:Type DataGridColumnHeader}">
              <Setter Property="FontSize" Value="12"/>
              <Setter Property="HorizontalAlignment" Value="Stretch"/>
              <Setter Property="HorizontalContentAlignment" Value="Center"/>
            </Style>
          </DataGrid.ColumnHeaderStyle>



          <!-- Styl wiersza  Tutaj jest warunek widoczności wiersza -->
          <DataGrid.CellStyle>
            <Style TargetType="DataGridCell">
              <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                  <Setter Property="Background" Value="LightYellow"/>
                  <Setter Property="BorderBrush" Value="LightYellow"/>
                </Trigger>
              </Style.Triggers>
              <Setter Property="Visibility" Value="{Binding widocznosc,UpdateSourceTrigger=PropertyChanged}"/>
            </Style>
          </DataGrid.CellStyle>



          <!-- Kolumny -->
          <DataGrid.Columns>
            <!-- Lp. -->
            <DataGridTemplateColumn Header="Lp.">
              <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                  <Label FontSize="12" Margin="3" HorizontalAlignment="Center"
                         VerticalAlignment="Center" Padding="0"
                         Content="{Binding lp,UpdateSourceTrigger=PropertyChanged}"/>
                </DataTemplate>
              </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <!-- Nazwa -->
            <DataGridTemplateColumn Header="Nazwa" Width="*">
              <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                  <Grid>
                    <Grid.ColumnDefinitions>
                      <ColumnDefinition Width="*"/>
                      <ColumnDefinition Width="auto"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                      <RowDefinition Height="auto"/>
                    </Grid.RowDefinitions>
                    <Label FontSize="12" Margin="3" HorizontalAlignment="Left"
                           VerticalAlignment="Center" Padding="0"
                           Grid.Row="0" Grid.Column="0" Content="{Binding nazwa,UpdateSourceTrigger=PropertyChanged}"/>
                    <StackPanel Orientation="Horizontal"
                                Grid.Row="0" Grid.Column="1">
                      <Button Width="25" Margin="2" ToolTip="Usuń"
                              Command="{Binding DataContext.usunTowar, 
                              RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"
                              IsEnabled="{Binding czyNieJestSkladowa,UpdateSourceTrigger=PropertyChanged}">
                        <Image Source="/Zapotrzebowanie stali;component/Icons/usun.png" Width="14"/>
                      </Button>
                      <Button Width="25" Margin="2" ToolTip="Zmień"
                              Command="{Binding DataContext.modyfikujTowar, 
                              RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}">
                        <Image Source="/Zapotrzebowanie stali;component/Icons/zmien.png" Width="14"/>
                      </Button>
                    </StackPanel>
                  </Grid>
                </DataTemplate>
              </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
          </DataGrid.Columns>
        </DataGrid>
      </TabItem>
    </TabControl>

Kiedy ustawię parametr DataGrid ScrollViewer.CanContentScroll na False wszystko chodzi bardzo płynnie (wyszukiwanie i przewijanie) ale ładowanie danych trwa potwornie długo. Dodatkowo problem rekordów po wyszukiwaniu (taki jak na drugim screenie) w ogóle nie występuje i DataGrid działa dobrze.

Dane ładuję w ten sposób:

this.OCTM_listaTowarow to obiekt typu ObservableCollection<TowarModel> gdzie TowarModel to klasa zawierająca pola takie jak wymienione w pętli.

        private void odczytajTowary(List<List<string>> wyn) {
            if (wyn != null) {
                this.OCTM_listaTowarow.Clear();
                for (int i = 0; i < wyn.Count; i++) {

                    //  Lista widziana w programie;
                    this.OCTM_listaTowarow.Add(new TowarModel {
                        lp = (i + 1).ToString(),
                        nazwa = wyn[i][1],
                        czyNieJestSkladowa = this.czyTowarNieNalezyDoProduktu(this.db.TowaryISkladniki_selectProduktyTowaru(wyn[i][1]))
                    });
                }
            }
            else Wiadomosci.utrataPolaczeniaSQL();
        }

Wyszukiwanie realizuję tak:
this.DTM_dodawanyTowar to właśnie fraza wyszukiwania i przy okazji towar, który można dodać do kolekcji jeżeli w niej nie występuje, ale to mniej ważne.

        private void _znajdzTowar() {
            string towar = this.DTM_dodawanyTowar.ToLower();
            string temp = null;

            if (towar != "") {
                for (int i = 0; i < this.OCTM_listaTowarow.Count; i++) {
                    this.OCTM_listaTowarow[i].widocznosc = "Visible";
                    temp = this.OCTM_listaTowarow[i].nazwa.ToLower();
                    if (!temp.Contains(towar))
                        this.OCTM_listaTowarow[i].widocznosc = "Collapsed";
                }
            }
            else { // Po skasowaniu frazy wyszukiwania do zera program pokazuje wszystkie rekordy
                for (int i = 0; i < this.OCTM_listaTowarow.Count; i++)
                    this.OCTM_listaTowarow[i].widocznosc = "Visible";
            }
        }

Bardzo proszę o pomoc, bo o ile przy małej ilości danych ustawienie ScrollViewer.CanContentScroll na False daje radę o tyle przy większej ilości rekordów ładowanie całości danych trwa wieki.

Pozdrawiam
Grzesiek.

0

Dodaj stronicowanie tego datagrida.

0

Właśnie ja nie chcę robić paginacji tego grida. :) To ma być jeden w całości wypełniony DataGrid.

1

Szkoda,że nie podałeś jak wygląda twoj ViewModel i trzeba to rąbać samemu, żeby Ci pomóc ;p

zakladam, że masz w nim coś takiego:

private ObservableCollection<Towar> _listaTowarow;
public ObservableCollection<Towar> OCTM_listaTowarow
{
	get { return _listaTowarow; }
	private set
	{
		_listaTowarow = value;
		OnPropertyChanged("OCTM_listaTowarow");
	}
}

proponuje zmienić na taki:

        private ObservableCollection<Towar> _listaTowarow;
        public ObservableCollection<Towar> OCTM_listaTowarow
        {
            get { return _listaTowarow; }
            private set
            {
                _listaTowarow = value;
                ItemsView = CollectionViewSource.GetDefaultView(_listaTowarow);
                OnPropertyChanged("OCTM_listaTowarow");
            }
        }

dodaj:

public ICollectionView ItemsView{get;set;}

a funkcje _znajdzTowar na:

  private void _znajdzTowar()
        {
            var towar = this.DTM_dodawanyTowar.ToLower();
            var list = (ListCollectionView)ItemsView;
            list.Filter = new Predicate<object>((obj) =>  (obj as Towar).nazwa.ToLower().Contains(towar) );
        }

Bibliografia:
http://msdn.microsoft.com/pl-pl/library/ff407126(v=vs.110).aspx
oraz
http://stackoverflow.com/a/6470271/4419424

Myślę, że różnica jest taka, że w Twoim podejsciu nie wiadomo czy dany wiersz powinien być wyświetlony dopoki grid rzeczywiście nie będzie chciał go wyświetlic(przez to, że jest tam binding na visibility) a przy użyciu ICollectionView od razu wiadomo ile elementów ma być wyświetlonych.

0

Ooo dobra, to już jest coś. Nie pokazywałem viewmodelu, bo myślałem, że problem leży jednak w widoku. Spróbuję zastosować na dniach to co napisałeś i zobaczymy. Dziękuję za poradę :)

0

No dobrze, problem jest już rozwiązany ale nie rozwiązałem go sposobem, który mi podałeś. Niestety Twoja metoda nie zadziałała i wywalało mi program. Mam trochę inny model i takie podejście nie zdało egzaminu.

Zrobiłem tak:

  1. Utworzyłem sobie mirror kolekcji 'OCTM_listaTowarow', z którego zrobiłem sobie mastera; Master nazywa się '_listaTowarowMaster'.
  2. Kiedy mam już dwie kolekcje to mastera używam jako magazyn danych do wyszukiwania, a do 'OCTM_listaTowarow' laduje wyniki wyszukiwania uprzednio ją czyszcząc.

Kodzik:

        private void _znajdzTowar() {
            string towar = this.DTM_dodawanyTowar.ToLower();
            string temp = null;

            //  Filtrowanie mastera i czyszczenie kolekcji dla widoku;
            if (towar != "") {
                for (int i = 0; i < this._listaTowarowMaster.Count; i++) {
                    this._listaTowarowMaster[i].widocznosc = "Visible";
                    temp = this._listaTowarowMaster[i].nazwa.ToLower();
                    if (!temp.Contains(towar))
                        this._listaTowarowMaster[i].widocznosc = "Collapsed";
                }
            }
            else {
                for (int i = 0; i < this._listaTowarowMaster.Count; i++)
                    this._listaTowarowMaster[i].widocznosc = "Visible";
            }
            this.OCTM_listaTowarow.Clear();

            //  Warunkowe przepisywanie mastera do kolekcji widoku;
            for (int i = 0; i < this._listaTowarowMaster.Count; i++)
                if (this._listaTowarowMaster[i].widocznosc == "Visible")
                    this.OCTM_listaTowarow.Add(this._listaTowarowMaster[i]);
        }

Kiedy kolekcja, która jest wyświetlana w widoku zostanie wyczyszczona i ponownie załadowana danymi rekordy tabeli się nie "rozjeżdżają". Chciałem zastosować bardziej tradycyjną metodę i udało się. :-) Może nie jest to tak bardzo optymalne ale w zupełności wystarcza i szybko działa nawet dla dużej liczby rekordów z czego jestem zadowolony.

Dziękuję za podpowiedź. Jak już mówiłem wcześniej nie szukałem przyczyn w ViewModelu, a bardziej w widoku.

Pozdrawiam i plusik za nakierowanie :)
Grzesiek

1 użytkowników online, w tym zalogowanych: 0, gości: 1