Не могу понять как забиндить правильно xml

117
28 июля 2019, 22:30

У меня есть xml файл

 <ballast>
  <tank number="32" ID="FPTW" Name="Forepeak" Weight="0,00" SG="1,025" Volume="0,00">
    <volume level="0.00" x="52.00" y="-0.90" z="0.00">0.00</volume>
    <volume level="0.10" x="53.48" y="0.00" z="0.05">0.77</volume>
    <volume level="0,20" x="53,70" y="0,00" z="0,11">1,99</volume>
    <volume level="0,30" x="53,88" y="0,00" z="0,18">3,61</volume>
    <volume level="0,40" x="54,02" y="0,00" z="0,24">5,54</volume>
    <volume level="0,50" x="54,16" y="0,00" z="0,30">7,80</volume>
    <volume level="0,60" x="54,29" y="0,00" z="0,36">10,36</volume>
    <volume level="0,70" x="54,39" y="0,00" z="0,42">13,12</volume>
  </tank>
</ballast>

Есть модель

public class Model
{
    public static string Path = "XMLFile1.xml";
}
[Serializable]
public class ModelBallast
{
    public int Number { get; set; }
    public string Id { get; set; }
    public string Name { get; set; }
    public double Weight { get; set; }
    public double Sg { get; set; }
    public double Volume { get; set; }
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

Есть vm но в ней только INotifyPropertyChanged

class BallastVM : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged([CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Есть view

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <TabControl>
        <TabItem Header="Ballast" Height="40" Width="100">
            <DataGrid x:Name="ballastdg" AutoGenerateColumns="False">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Width="3*"/>
                    <DataGridTextColumn Header="Weight" Width="*"/>
                    <DataGridTextColumn Header="SG" Width="*"/>
                    <DataGridTextColumn Header="Volume" Width="*"/>
                </DataGrid.Columns>
            </DataGrid>
        </TabItem>
    </TabControl>
    <DataGrid Grid.Row="1" x:Name="dcdg" >
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Width="3*" />
            <DataGridTextColumn Header="Volume" Width="*"/>
            <DataGridTextColumn Header="X" Width="*"/>
            <DataGridTextColumn Header="Y" Width="*"/>
            <DataGridTextColumn Header="Z" Width="*"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

И мне надо

  1. Binding xml на ballastdg
  2. Разрешить юзеру редактировать всё кроме имени с влиянием на другие элементы (weight = sg * volume)
  3. При volume !=0 показать строку в dcdg с тем показателем volume и соответствующим ему координатам
Answer 1

Для начала подготовим проект:

  1. Я создам две папки Models и ViewModels.
  2. В ViewModels создадим новый класс, который будет реализовывать INotifyPropertyChanged, назовем его к примеру BaseVM и перенесем туда весь ваш код.
  3. Также в ViewModels создадим еще класс, который укажем как DataContext, у меня будет это MainViewModel.
  4. В MainWindow.xaml.cs зададим DataContext. После InitializeComponent(); пишем DataContext = new MainViewModel();.

Все, базовую подготовку мы сделали, теперь по порядку:

Binding xml на ballastdg

Xml по сути можно считать моделью, которая будет иметь методы для загрузки данных и их отдачи в VМ.

  1. Создадим в Models класс DataModel (имена указывайте реальные, у меня это чисто для примера), а также класс ItemModel.
  2. В классе ItemModel я пропишу все необходимые для примера свойства:

    class ItemModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Value { get; set; }
    }
    
  3. В DataModel сделаем имитацию получения данных, пусть отдает нам некоторые тестовые данные:

    class DataModel
    {
        public List<ItemModel> GetItems() =>
            new List<ItemModel>
            {
                new ItemModel(1, "Item 1", 33),
                new ItemModel(2, "Item 2", 14),
                new ItemModel(2, "Item 3", 0),
            };
    }
    
  4. Теперь мы имеем модель, которая готова отдавать нам данные, поработаем с ViewModel. Создадим в папке ViewModels класс с названием ItemViewModel и реализуем в нем все свойства для привязок и необходимую логику (если есть):

    class ItemViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Value { get; set; }
    }
    
  5. В MainViewModel подключаем модель, создаем публичное свойство нашей коллекции для привязки и сделаем метод для заполнения ее:

    class MainViewModel
    {
        private DataModel Data;
        public ObservableCollection<ItemViewModel> Items { get; set; }
        public MainViewModel()
        {
            Data = new DataModel();
            Items = new ObservableCollection<ItemViewModel>();
            Load();
        }
        public void Load()
        {
            foreach (var item in Data.GetItems())
                Items.Add(new ItemViewModel(item.Id, item.Name, item.Value));
        }
    }
    
  6. Теперь View. Для удобства в Xaml укажем тип наших данных для дизайнера:

    d:DataContext="{d:DesignInstance {x:Type vm:MainViewModel}}"
    
  7. Напишем теперь простенькую разметку:

    <DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Id" Width="*" Binding="{Binding Id}"/>
            <DataGridTextColumn Header="Название" Width="3*" Binding="{Binding Name}"/>
            <DataGridTextColumn Header="Значение" Width="*" Binding="{Binding Value}"/>
        </DataGrid.Columns>
    </DataGrid>
    

Все, ваши данные будут успешно отображены в приложении:

Вам тут останется реализовать сохранение значений при изменении и прочие мелочи.

Разрешить юзеру редактировать всё кроме имени с влиянием на другие элементы (weight = sg * volume)

Для запрета изменений сделайте свойство без Set, допустим в моем примере убираем возможность изменить Id:

  • В ItemViewModel переделываем Id на public int Id { get; }
  • В View меняем режим привязки на односторонний {Binding Id, Mode=OneWay}.

Для изменения других значений объекта в Set нужного свойства добавьте логику, но не стоит забывать, что для оповещения интерфейса об изменении свойства, нужен INPC.
Давайте для примера я создам еще одно свойство, которое будет отображать значение умноженное на 2:

  • Наследуем ItemViewModel от ранее созданного BaseVM - class ItemViewModel : BaseVM
  • Реализуем свойство с оповещением об изменении:

    private int _customValue;
    public int CustomValue
    {
        get => _customValue;
        set
        {
            _customValue = value;
            RaisePropertyChanged();
        }
    }
    
  • Изменяем старое свойство, добавляя ему нужную логику:

    private int _value;
    public int Value
    {
        get => _value;
        set
        {
            _value = value;
            CustomValue = value * 2;
        }
    }
    
  • Привязываем: <DataGridTextColumn Header="Значение x2" Width="*" Binding="{Binding CustomValue}"/>

Смотрим результат:

При volume !=0 показать строку в dcdg с тем показателем volume и соответствующим ему координатам

Для этих целей да, лучше использовать фильтрацию. Я покажу на примере ListCollectionView.

  • В MainViewModel создаем новое свойство, к которому будет привязан другой Control и в нем мы будем отображать только отфильтрованные значения:

    public ListCollectionView FilteredItems { get; set; }
    
  • Далее в конструкторе MainViewModel инициализируем это свойство, задав ему все что нам необходимо:

    FilteredItems = new ListCollectionView(Items)
    {
        Filter = item => ((ItemViewModel)item).Value != 0,
        IsLiveFiltering = true,
        LiveFilteringProperties =
        {
            nameof (ItemViewModel.Value)
        }
    };
    
  • По аналогии с вашей View я сделаю снизу отфильтрованные данные:

    <DataGrid Grid.Row="1" ItemsSource="{Binding FilteredItems}" AutoGenerateColumns="False" IsReadOnly="True">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Название" Width="3*" Binding="{Binding Name}"/>
            <DataGridTextColumn Header="Значение" Width="*" Binding="{Binding Value}"/>
        </DataGrid.Columns>
    </DataGrid>
    

Запускаем и смотрим результат:

Фуф, ну вроде все.
На будущее: пожалуйста, не задавайте несколько вопросов в одном!

READ ALSO
Как правильно записать List&lt;string&gt; в БД с помощью Entity Framework?

Как правильно записать List<string> в БД с помощью Entity Framework?

Нужна база с такими двумерными списками, как ее правильно записать? Вряд ли получится сделать public DbSet<List<List<string>>> lst { get; set; } (я не пробовал)

154
Как открыть модальное окно ShowDialog() в FrameWork 4.0? Исключение in PresentationCore.dll

Как открыть модальное окно ShowDialog() в FrameWork 4.0? Исключение in PresentationCore.dll

Подскажите пожалуйста, разрабатываю приложение WPF под FrameWork 40, т

118
Фабрика для EF core в режиме dbcontext pool

Фабрика для EF core в режиме dbcontext pool

Необходимо сделать фабрику (проще лямбду конечно), которая бы выдавала свободный dbcontext из пула

155