I'm trying to create a datagrid which has a entries which have properties coming from a json. The problem is that a lot of the properties should be dynamically generated based on the information that is in the json.
The first thing I tried was to make a very long list of all possible properties with their own getters & seters. But it's a very long list and it felt like there should be an automated solution for this problem. This is included at the end of Datagrid Entry View Model. So I am able to bind to Number when:
Number = obj.NUMBER;
After that I researched into dynamic types and expandoObjects. I learned that apperently it's not possible to bind WPF to an expandoObject's expanded properties. So I cannot bind to expando.Number
expando.Number = obj.NUMBER
I also tried to follow this example in which helper classes are created to help with the binding, but I can't seem to get it to work. When I run my solution I get an error saying that the property "Numberz" doesn't exist. So I cannot bind to Numberz:
AddProperty("Numberz", typeof(string));
SetPropertyValue("Numberz", obj.NUMBER);
At moment I'm out of ideas, so if anybody here could help, I would be very grateful. Maybe a few last notes. I'm not a very experienced programmer and I'm trying to work through an MVVM-pattern.
Datagrid entry view model
public class DatagridEntryViewModel : ICustomTypeProvider, INotifyPropertyChanged
{
#region Fields
/// <summary>
/// Custom Type provider implementation - delegated through static methods.
/// </summary>
private readonly CustomTypeHelper<DatagridEntryViewModel> _helper = new CustomTypeHelper<DatagridEntryViewModel>();
#endregion
#region Constructor
public DatagridEntryViewModel(ExitCoil model)
{
_helper.PropertyChanged += (s, e) => PropertyChanged(this, e);
dynamic Json = JObject.Parse(model.JsonCoilData);
//CreationDatePost = Json.CREATION_DATE_S_POST;
////Number = Json.NUMBER;
//CreationDatePost = Json.CREATION_DATE_S_POST;
////LeverageOrder = Json.LEVERAGE_ORDER;
//Client = Json.CLIENT;
//Skinned = Json.SKINNED;
dynamic expando = new ExpandoObject();
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(Json.ToString());
//AddProperty("Numberz", typeof(string));
//SetPropertyValue("Numberz", obj.NUMBER);
//expando.AddItem("Number");
//expando[0] = obj.NUMBER;
//Number = expando.Number;
Model = model;
//var settings = new JsonSerializerSettings();
//settings.Converters.Add(new Models.DynamicJsonConverter());
//var serializer = JsonSerializer.Create(settings);
//JsonReader reader = new JsonTextReader(new StringReader(JsonString));
//var Data = serializer.Deserialize<DynamicObject>(reader);
}
#endregion
#region Model & proxy
private ExitCoil _model;
public ExitCoil Model
{
set { _model = value; }
}
#endregion
#region Public Methods
// Methods to support dynamic properties.
public static void AddProperty(string name)
{
CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name);
}
public static void AddProperty(string name, Type propertyType)
{
CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name, propertyType);
}
public static void AddProperty(string name, Type propertyType, List<Attribute> attributes)
{
CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name, propertyType, attributes);
}
public void SetPropertyValue(string propertyName, object value)
{
_helper.SetPropertyValue(propertyName, value);
}
public object GetPropertyValue(string propertyName)
{
return _helper.GetPropertyValue(propertyName);
}
public PropertyInfo[] GetProperties()
{
return _helper.GetProperties();
}
Type ICustomTypeProvider.GetCustomType()
{
return _helper.GetCustomType();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Properties
//public string Number { get; private set; }
//public string CreationDatePost { get; private set; }
//public string LeverageOrder { get; private set; }
public string Client { get; private set; }
//public int OrderZnTop { get; private set; }
//public int OrderZnBottom { get; private set; }
//public string OrderAspect { get; private set; }
//public string OderSideTrimming { get; private set; }
//public string OrderPassivation { get; private set; }
//public string OrderOilType { get; private set; }
//public string OrderOilQuantity { get; private set; }
#endregion
}
Datagrid view model
public class DatagridViewModel : MainViewModelBase
{
#region Singleton
public static DatagridViewModel Instance = new DatagridViewModel();
#endregion
#region Fields
private ObservableCollection<DatagridEntryViewModel> _coilEntries = new ObservableCollection<DatagridEntryViewModel>();
private readonly IViewManager _viewManager;
private readonly IDockManager _dockManager;
#endregion
#region Properties
public ObservableCollection<DatagridEntryViewModel> CoilEntries
{
get { return _coilEntries; }
set
{
_coilEntries = value;
RaisePropertyChanged(() => CoilEntries);
}
}
public ProductionExitCoilReport Report { get; set; }
#endregion
#region Constructor
public DatagridViewModel()
{
_viewManager = Globals.Container.Resolve<IViewManager>();
_dockManager = Globals.Container.Resolve<IDockManager>();
getReport();
foreach (var Coil in Report.Coils)
{
CoilEntries.Add(new DatagridEntryViewModel(Coil));
}
}
#endregion
#region Methods
public void getReport()
{
var proxy = new Proxy<IReportService>();
proxy.Open();
var LijnId = new LijnIdentificatie { Department = "HDG", Firm = "LIE", LineNumber = "05" };
var Periode = new Period { FromDate = new DateTime(2017 - 11 - 10), FromShift = 1, ToDate = new DateTime(2017 - 11 - 10), ToShift = 1 };
var request = new ProductionReportRequest() { LijnIdentificatie = LijnId, Period = Periode };
Report = proxy.Service.GetProductionExitCoilReport(request);
}
#endregion
}
Datagrid XAML
<DataGrid x:Class="Promo.Presentation.Views.CoilGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/marukp-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Promo.Presentation.Views"
xmlns:components="clr-namespace:Promo.Presentation.Components"
xmlns:Data="clr-namespace:Promo.Presentation.ViewModels"
AutoGenerateColumns="False"
GridLinesVisibility="none"
Background="White"
Height="Auto"
Width="Auto"
MinWidth="500"
MinHeight="500"
DataContext="{x:Static Data:DatagridViewModel.Instance}"
ItemsSource="{Binding CoilEntries}"
IsReadOnly="True"
HeadersVisibility="Column"
Padding="10"
>
<DataGrid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/Colors.xaml"/>
<ResourceDictionary Source="../Styles/Datagrid.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Numberz}" Header="Rolnummer" MaxWidth="125"/>
<DataGridTextColumn Binding="{Binding CreationDatePost}" Header="Post" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding LeverageOrder}" Header="Leverage order" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding Client}" Header="Client" MaxWidth="400"/>
<DataGridTextColumn Binding="{Binding OrderZnTop}" Header="Order zinc top" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding OrderZnBottom}" Header="Order zinc bottom" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding OrderAspect}" Header="Order aspect" MaxWidth="125"/>
<DataGridCheckBoxColumn Binding="{Binding OrderSideTrimming, Converter={local:StringToBoolConverter}}" Header="Order Side Trimming" MaxWidth="200"/>
<DataGridTextColumn Binding="{Binding OrderPassivation}" Header="Order Passivation" MaxWidth="200"/>
<DataGridCheckBoxColumn Binding="{Binding Skinned, Converter={local:StringToBoolConverter}}" Header="Skinned" MaxWidth="200"/>
</DataGrid.Columns>
</DataGrid>
Part of the Json
{"NUMBER":"G571015110 ","CREATION_DATE_S_POST":"2017-05-11T00:00:00""LEVERAGE_ORDER":"FD72BIGA5W/8","CLIENT":"ARCELORMITTAL BELGIUM SA "}
If you have a json with all of the properties then Visual Studio can generate class for you! no need to create them your self. I worked on a similar scenario in the past, this has been done before.
To paste a class generated from json click in your .cs file where you want to generate the code, then click on Edit in the Visual Studio menu, next select Paste Special and then select Paste JSON As Classes.
This should make it a bit easier for you.
Related
I have a DataGrid that its source is DB based. the code is not in MVVM.
Now, i need the DataGridComboBoxColumn source to changed based on a different DataGridComboBoxColumn value thats in the same DataGrid- I'm sure there is a simple solution, but still, couldnt figure it out- how do i do that?
My code:
XAML:
<DataGridComboBoxColumn x:Name="active_idnt_deviceCmb" SelectedValueBinding="{Binding idnt_linked_io_device}" DisplayMemberPath="correct_idnt_active_logic_device" SelectedValuePath="idnt_active_device" Header="input id" Width="80"></DataGridComboBoxColumn>
<DataGridComboBoxColumn x:Name="active_device_addressCmb" ElementStyle="{StaticResource MyComboBoxStyle}" SelectedValueBinding="{Binding idnt_linked_io_device}" DisplayMemberPath="active_device_address" SelectedValuePath="active_device_address" Header="Relay Address" Width="65"><DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<EventSetter Event="SelectionChanged" Handler="changeDeviceAddress" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
cs:
private void changeDeviceAddress(object sender, SelectionChangedEventArgs e)
{
HelpDataSet.ACTIVE_IO_DEVICESDataTable dtActiveIo = new HelpDataSet.ACTIVE_IO_DEVICESDataTable();
var comboBox = sender as ComboBox;
if (comboBox.SelectedValue != null)
{
AppHelp.ActiveIODeviceAdapter.ClearBeforeFill = true;
AppHelp.ActiveIODeviceAdapter.FillByIdntRelayAddress(dtActiveIo, comboBox.SelectedValue.ToString());
active_idnt_deviceCmb.ItemsSource = dtActiveIo.DefaultView;
}
}
but it changes the whole column source and not just of the specific cell in the row.
here is solution which doesn't use the MVVM approach at all, it based on the WPF selection oriented behavior. Please keep in mind that all models just presents a data grid rows, they does'n have any additional functionality. The Source collection collecting is triggered in the behavior by a combo selection trigger.
Here is the code:
1. XAML code:
<Window x:Class="ComboWithoutCodeBehind.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:comboWithoutCodeBehind="clr-namespace:ComboWithoutCodeBehind"
Title="MainWindow" Height="350" Width="525" x:Name="This">
<Grid >
<DataGrid x:Name="SelectDataGrid"
ItemsSource="{Binding ElementName=This, Path=Persons}" HorizontalAlignment="Left" VerticalAlignment="Top" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn x:Name="dgCheckBox" Header="Select" Width="45" Binding="{Binding IsChecked}"/>
<DataGridTextColumn Header="FIRST NAME" Width="125" Binding="{Binding FNAME}"/>
<DataGridTextColumn Header="LAST NAME" Width="125" Binding="{Binding LNAME}"/>
<DataGridTemplateColumn Header="Selection" Width="120">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" x:Name="DataGridTemplateColumnComboBox" SelectedIndex="0" ItemsSource="{Binding ElementName=This, Path=ServersCollection}"
DisplayMemberPath="ServerName"></ComboBox>
<ComboBox Grid.Column="1" DisplayMemberPath="DbName">
<i:Interaction.Behaviors>
<comboWithoutCodeBehind:ItemsSourcePosessingBehavior
SourceProvidingFactory="{Binding ElementName=This, Path=MainSourceProvidingFactory}"
SiblingComboBox="{Binding ElementName=DataGridTemplateColumnComboBox}"/>
</i:Interaction.Behaviors>
</ComboBox>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
2. Code behind, models and factory code:
public partial class MainWindow : Window
{
public static readonly DependencyProperty PersonsProperty = DependencyProperty.Register("Persons",
typeof (ObservableCollection<Person>), typeof (MainWindow),
new PropertyMetadata(default(ObservableCollection<Person>)));
public static readonly DependencyProperty MainSourceProvidingFactoryProperty =
DependencyProperty.Register("MainSourceProvidingFactory", typeof (SourceProvidingFactory),
typeof (MainWindow), new PropertyMetadata(default(SourceProvidingFactory)));
public ObservableCollection<Person> Persons
{
get { return (ObservableCollection<Person>)GetValue(PersonsProperty); }
set { SetValue(PersonsProperty, value); }
}
public SourceProvidingFactory MainSourceProvidingFactory
{
get { return (SourceProvidingFactory)GetValue(MainSourceProvidingFactoryProperty); }
set { SetValue(MainSourceProvidingFactoryProperty, value); }
}
public MainWindow()
{
MainSourceProvidingFactory = new SourceProvidingFactory(GetCollection);
InitSources();
InitializeComponent();
}
private ObservableCollection<DbDetails> GetCollection(object arg)
{
//you can perform your db relate logic here
var sereverDetails = arg as ServerDetails;
return sereverDetails == null ? null : new ObservableCollection<DbDetails>(sereverDetails.DbDetailses);
}
private void InitSources()
{
var l = new List<Person>
{
new Person {FNAME = "John", LNAME = "W"},
new Person {FNAME = "George", LNAME = "R"},
new Person {FNAME = "Jimmy", LNAME = "B"},
new Person {FNAME = "Marry", LNAME = "B"},
new Person {FNAME = "Ayalot", LNAME = "A"},
};
Persons = new ObservableCollection<Person>(l);
ServersCollection = new ObservableCollection<ServerDetails>(new List<ServerDetails>
{
new ServerDetails
{
ServerName = "A",
DbDetailses = new List<DbDetails>
{
new DbDetails {DbName = "AA"},
new DbDetails {DbName = "AB"},
new DbDetails {DbName = "AC"},
}
},
new ServerDetails
{
ServerName = "B",
DbDetailses = new List<DbDetails>
{
new DbDetails {DbName = "BA"},
new DbDetails {DbName = "BB"},
new DbDetails {DbName = "BC"},
}
},
new ServerDetails
{
ServerName = "C",
DbDetailses = new List<DbDetails>
{
new DbDetails {DbName = "CA"},
new DbDetails {DbName = "CB"},
}
}
});
}
public ObservableCollection<ServerDetails> ServersCollection { get; set; }
}
public class SourceProvidingFactory
{
public SourceProvidingFactory(Func<object, ObservableCollection<DbDetails>> action)
{
GetCollection = action;
}
public Func<object, ObservableCollection<DbDetails>> GetCollection { get; set; }
}
public class Person : BaseObservableObject
{
private string _lName;
private string _fName;
private bool _checked;
public bool IsChecked
{
get { return _checked; }
set
{
_checked = value;
OnPropertyChanged();
}
}
public string LNAME
{
get { return _lName; }
set
{
_lName = value;
OnPropertyChanged();
}
}
public string FNAME
{
get { return _fName; }
set
{
_fName = value;
OnPropertyChanged();
}
}
}
public class ServerDetails : BaseObservableObject
{
private string _serverName;
public string ServerName
{
get { return _serverName; }
set
{
_serverName = value;
OnPropertyChanged();
}
}
public List<DbDetails> DbDetailses { get; set; }
}
public class DbDetails : BaseObservableObject
{
private string _dbName;
public string DbName
{
get { return _dbName; }
set
{
_dbName = value;
OnPropertyChanged();
}
}
}
3. Behavior code:
public class ItemsSourcePosessingBehavior : Behavior<ComboBox>
{
public static readonly DependencyProperty SiblingComboBoxProperty = DependencyProperty.Register(
"SiblingComboBox", typeof(ComboBox), typeof(ItemsSourcePosessingBehavior), new PropertyMetadata(default(ComboBox)));
public ComboBox SiblingComboBox
{
get { return (ComboBox)GetValue(SiblingComboBoxProperty); }
set { SetValue(SiblingComboBoxProperty, value); }
}
public static readonly DependencyProperty SourceProvidingFactoryProperty = DependencyProperty.Register(
"SourceProvidingFactory", typeof (SourceProvidingFactory), typeof (ItemsSourcePosessingBehavior), new PropertyMetadata(default(SourceProvidingFactory)));
public SourceProvidingFactory SourceProvidingFactory
{
get { return (SourceProvidingFactory) GetValue(SourceProvidingFactoryProperty); }
set { SetValue(SourceProvidingFactoryProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
SiblingComboBox.SelectionChanged += SiblingComboBoxOnSelectionChanged;
SiblingComboBox.Loaded += SiblingComboBoxOnLoaded;
}
private void SiblingComboBoxOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
AssociatedObject.ItemsSource = null;
var siblingCombo = sender as ComboBox;
InitAssociatedObjectItemsSource(siblingCombo);
}
private void SiblingComboBoxOnSelectionChanged(object sender, SelectionChangedEventArgs selectionChangedEventArgs)
{
AssociatedObject.ItemsSource = null;
var siblingCombo = sender as ComboBox;
InitAssociatedObjectItemsSource(siblingCombo);
}
private void InitAssociatedObjectItemsSource(ComboBox siblingCombo)
{
if (siblingCombo == null)
{
return;
}
if (SourceProvidingFactory == null)
{
return;
}
AssociatedObject.ItemsSource = SourceProvidingFactory.GetCollection(siblingCombo.SelectedItem);
}
protected override void OnDetaching()
{
base.OnDetaching();
SiblingComboBox.SelectionChanged -= SiblingComboBoxOnSelectionChanged;
SiblingComboBox.Loaded -= SiblingComboBoxOnLoaded;
}
}
This is a complete solution just copy/past...
I'll be glad to help if you will have any problems with the code.
Regards.
I think it's a very basic problem we face in such scenario. now as you don't have problem with display and binding I'll just write down what I think the problem is.
Don't use active_idnt_deviceCmb.ItemsSource this will reset all the Combobox Item Source. You want different option in different rows based on the other column value. So you need to maintain one to one relationship for each record to each dropdown.
The best way to do so is what ever class is binded to per record you need to create that many data sources. if List<MyRecord> is itemsource of grid then design the class like:
public class MyRecord : INotifyPropertyChanged
{
private string a;
public string A
{
get { return a; }
set { a = value;
OnPropertyChanged("A");
OnPropertyChanged("SecondList");
}
}
private string b;
public string B
{
get { return b; }
set { b = value;
OnPropertyChanged("B");
}
}
private List<ComboBoxItem> firstList;
public List<ComboBoxItem> FirstList
{
get { return firstList; }
set { firstList = value;
OnPropertyChanged("FirstList");
}
}
private List<ComboBoxItem> secondList;
public List<ComboBoxItem> SecondList
{
get { return secondList.Where(x=>x.value.StartsWith(A)).ToList(); }
set { secondList = value;
OnPropertyChanged("SecondList");
}
}
}
PS: First column of grid is bind with A, second with B, and firstlist and secondlist is bind to dropdown of both columns. every time I select the value in first column it will reflected back to property A and at that point I'm refreshing the secondlist(which also contain filter logic also, or you can call filter logic and refresh secondlist separately).
As this second list is bind with only one record only one dropdown will reset.
XAML Code for understanding:
<DataGridComboBoxColumn x:Name="active_idnt_deviceCmb"
SelectedValuePath="value"
DisplayMemberPath="display"
SelectedValueBinding="{Binding A}"
Header="input id" Width="80">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=FirstList}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridComboBoxColumn
SelectedValuePath="value"
DisplayMemberPath="display"
SelectedValueBinding="{Binding B}"
Header="Relay Address" Width="65">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=SecondList}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
Combobox item source class(just to understand DataGridComboBoxColumn binding):
public class ComboBoxItem
{
public ComboBoxItem(string a)
{
display = value = a;
}
public string display { get; set; }
public string value { get; set; }
}
hopefully You'll be able to fit this code in your application. The main thing is One to One relationship that is important.
**[Solved]**this is my first question asked here, so some mistakes could pass.
In my WPF app I want to bind in "two-way mode" all controls to corresponding properties in special object instance BuildingManagementSystem, which I'd like to set as Window DataContext.
But nothing works (data are not displayed). What is the right way to bind it?
Here is my code:
public partial class MainWindow : Window
{
BuildingManagementSystem bms { get; set; }
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Window1_Loaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
bms = new BuildingManagementSystem();
this.DataContext = bms;
}
public class BuildingManagementSystem
{
public string Name { get; set; }
public readonly FireAlarmSystem FireAlarmSystem;
public BuildingManagementSystem()
{
FireAlarmSystem = new FireAlarmSystem();
}
}
class FireAlarmSystem
{
private int alarmSmokeRate, currentSmokeRate;
public List<PowerConsumer> Devices { get; set; }
public int CurrentSmokeRate
{
get { return currentSmokeRate; }
set { SetField(ref currentSmokeRate, value, () => CurrentSmokeRate); }
}
public FireAlarmSystem()
{
Devices = new List<PowerConsumer>();
}
}
class PowerConsumer
{
public string Name { get; set; }
public double Power { get; set; }
public int Priority { get; set; }
public bool Enabled { get; set; }
}
XAML:
<DataGrid Name="FireAlarmGrid" HorizontalAlignment="Left" Margin="10,51,0,0" CanUserAddRows="True"
CanUserDeleteRows="True" VerticalAlignment="Top" AutoGenerateColumns="False" ItemsSource="{Binding FireAlarmSystem.Devices}" >
<DataGrid.RowValidationRules>
<local:FireValidationRule ValidationStep="UpdatedValue"/>
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Enabled" Binding="{Binding Enabled}"></DataGridCheckBoxColumn>
<DataGridTextColumn Header="Name" Binding="{Binding Name,ValidatesOnExceptions=True }" >
</DataGridTextColumn>
<DataGridTextColumn Header="Power" Binding="{Binding Power}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<TextBox Name="tbCurrentSmokeRate" Text="{Binding Path=FireAlarmSystem.CurrentSmokeRate, Mode=TwoWay}" VerticalAlignment="Top" Width="70"/>
I personally like to create static instances of the viewmodel as shown here:
<Window.Resources>
<ViewModels:MainWindowVM x:Key="VM"></ViewModels:MainWindowVM>
When I do that then binding is easy because the properites page finds the properties for you in VS.
<StackPanel Grid.Column="0" Margin="5,5,5,0"
DataContext="{Binding Source={StaticResource VM}}">
But remember you need to add the namespace
xmlns:ViewModels="clr-namespace:MyWPF.ViewModels"
xmlns:Views="clr-namespace:MyWPF.Views"
This allow property binding like this:
<Views:UcTitle x:Name="XTitle" ></Views:UcTitle>
<Views:UcLegendTitle x:Name="XLegendTitle"/>
<Views:UcSeriesTitle x:Name="XSeriesTitle" />
<Views:UcSeriesTypes x:Name="XSeriesTypes"/>
And you don't have to type any of the names in...
In your case you are not using a ViewModel but you are setting the data-context correctly. So this can only be either no data to display or improper property bind. Remember for this to work you need three things 1) DataContext 2) Vales and 3) Proper binding by Name... It's the third one that trips up folks a lot when just starting out with the great WPF binding system.
Couple of mistakes out here
Seems like BuildingManagementSystem is intended to be your data-context
In place of BuildingManagementSystem bms { get; set; } write the below code:
BuildingManagementSystem bms =new BuildingManagementSystem ();
You need to implement INotifyPropertyChanged event in your ViewModels to reflect the changes on UI
If you intent is to assign values to each of the underlying child view-models, make use of parameterized constructors to pass and assign values. Simply instantiating the child viewModels in parent will not serve any purpose.
To get MVVM working...
You will need to update your View Model to Implement INotifyPropertyChanged to let wpf know your property changed
Use Observable collections instead of lists, ObservableCollections have the wiring to let Datagrid listview, listboxes know items in the collections have changed
Change the Field to a Property so WPF will see it...
<DataGrid Name="FireAlarmGrid" HorizontalAlignment="Left" Margin="10,51,0,0" CanUserAddRows="True"
CanUserDeleteRows="True" VerticalAlignment="Top" AutoGenerateColumns="False" ItemsSource="{Binding FireAlarmSystem.Devices}" >
<DataGrid.RowValidationRules>
<local:FireValidationRule ValidationStep="UpdatedValue"/>
</DataGrid.RowValidationRules>
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Enabled" Binding="{Binding Enabled,Mode=TwoWay}"></DataGridCheckBoxColumn>
<DataGridTextColumn Header="Name" Binding="{Binding Name,ValidatesOnExceptions=True,Mode=TwoWay }" >
</DataGridTextColumn>
<DataGridTextColumn Header="Power" Binding="{Binding Power,Mode=TwoWay}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<TextBox Name="tbCurrentSmokeRate" Text="{Binding Path=FireAlarmSystem.CurrentSmokeRate, Mode=TwoWay}" VerticalAlignment="Top" Width="70"/>
public class BuildingManagementSystem : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
private FireAlarmSystem _fireAlarmSystem;
public FireAlarmSystem FireAlarmSystem { get { return _fireAlarmSystem; } }
public BuildingManagementSystem()
{
_fireAlarmSystem = new FireAlarmSystem();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
#endregion
}
public class FireAlarmSystem : INotifyPropertyChanged
{
private int alarmSmokeRate, currentSmokeRate;
public ObservableCollection<PowerConsumer> Devices { get; set; }
public int CurrentSmokeRate
{
get { return currentSmokeRate; }
set
{
//SetField(ref currentSmokeRate, value, () => CurrentSmokeRate);
PropertyChanged(this, new PropertyChangedEventArgs("CurrentSmokeRate"));
}
}
public FireAlarmSystem()
{
Devices = new ObservableCollection<PowerConsumer>();
//Create some test data...
Devices.Add(new PowerConsumer() { Name = "One", Enabled = true, Power = 100, Priority = 1 });
Devices.Add(new PowerConsumer() { Name = "two", Enabled = false, Power = 101, Priority = 2 });
Devices.Add(new PowerConsumer() { Name = "three", Enabled = false, Power = 103, Priority = 3 });
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
#endregion
}
public class PowerConsumer
{
public string Name { get; set; }
public double Power { get; set; }
public int Priority { get; set; }
public bool Enabled { get; set; }
}
Thanks to all, I've found a mistake, it was defining FireAlarmSystem as readonly, it shold be a property.
2 Stuart Smith, you're right with INotifyPropertyChanged, but I have an abstract class-ancestor BuildingSystem, which implements this interface. I've forgot to post it.
Having some problems displaying strings in a datagrid.
To explain the code: I am binding a collection of Soldiers to a ComboBox. A Soldier has its own collection of weapons.
When I select a specific soldier in the ComboBox, I want that soldier's weapons displayed in the datagrid. I believe I'm binding correctly, but the datagrid always comes up blank. Anybody know what i'm doing wrong?
XAML
<Grid>
<ComboBox x:Name="Character_ComboBox" HorizontalAlignment="Left" VerticalAlignment="Top" Width="328" Height="25">
</ComboBox>
</Grid>
<DataGrid x:Name="Character_items_datagrid" ItemsSource="{Binding ElementName=Character_ComboBox, Path= SelectedItem.Equipment, Mode=OneWay}" Margin="328,0,0,0" Grid.RowSpan="2" >
<DataGrid.Columns>
<DataGridTextColumn Header="Primary" Binding="{Binding Primary, Mode=TwoWay}" FontWeight="Bold" Foreground="Black" Width="0.1*"></DataGridTextColumn>
<DataGridTextColumn Header ="Secondary" Binding="{Binding Secondary, Mode=TwoWay}" Width="0.1*"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Soldier Class
public class Soldier
{
public string Soldier_Class { get; set; }
public ObservableCollection<Weapons> Equipment { get; set; }
}
Weapons Class
public class Weapons
{
string Primary { get; set; }
string Secondary { get; set; }
public Weapons(string primary, string secondary)
{
this.Primary = primary;
this.Secondary = secondary;
}
}
MainWindow
public ObservableCollection<Soldier> squad_members = new ObservableCollection<Soldier>();
public MainWindow()
{
InitializeComponent();
squad_members.Add(new Soldier() { Soldier_Class = "Assult Soldier", Equipment = new ObservableCollection<Weapons>() { new Weapons("M4 Rifle", "Compact 45 Pistol")}});
squad_members.Add(new Soldier() { Soldier_Class = "SMG Soldier", Equipment = new ObservableCollection<Weapons>() { new Weapons("RPK Machine Gun", "HK Shotgun"), new Weapons("SAW Machine Gun", "Compact 45 Pistol")}});
squad_members.Add(new Soldier() { Soldier_Class = "Juggernaut", Equipment = new ObservableCollection<Weapons>() { new Weapons("MP5", "Bowie Knife") }});
Binding comboBinding = new Binding();
comboBinding.Source = squad_members;
BindingOperations.SetBinding(Character_ComboBox, ComboBox.ItemsSourceProperty, comboBinding);
Character_ComboBox.DisplayMemberPath = "Soldier_Class";
Character_ComboBox.SelectedValuePath = "Soldier_Class";
}
Result:
You need to make properties in the model public for binding to be able to work :
public class Weapons
{
public string Primary { get; set; }
public string Secondary { get; set; }
.....
}
Your DataGrid looks populated with items correctly, just the properties of each item are not correctly displayed in the columns. This is indication that binding engine can't access the item's properties due to it's private accessibility.
Your primary problem is the public access modifier, as har07 wrote.
There are a lot of other things you can improve as well. Implement INotifyPropertyChanged for your classes, so any change to the properties is immediately reflected by the UI. Without compelling reasons, do not create bindings in code. Use a ViewModel to bind to, instead of binding directly to elements like ComboBox.SelectedItem. Set AutoGenerateColumns to false if you want to style your columns (your code would produce four columns). Use Grid.ColumnDefinitions instead of assigning a fixed margin.
Models:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication1.ViewModels
{
public class SquadViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Soldier> _squadMembers;
public ObservableCollection<Soldier> SquadMembers { get { return _squadMembers; } set { _squadMembers = value; OnPropertyChanged("SquadMembers"); } }
private Soldier _selectedSoldier;
public Soldier SelectedSoldier { get { return _selectedSoldier; } set { _selectedSoldier = value; OnPropertyChanged("SelectedSoldier"); } }
public SquadViewModel()
{
SquadMembers = new ObservableCollection<Soldier>()
{
new Soldier() { SoldierClass = "Assult Soldier", Equipment = new ObservableCollection<Weapon>() { new Weapon("M4 Rifle", "Compact 45 Pistol") } },
new Soldier() { SoldierClass = "SMG Soldier", Equipment = new ObservableCollection<Weapon>() { new Weapon("RPK Machine Gun", "HK Shotgun"), new Weapon("SAW Machine Gun", "Compact 45 Pistol") } },
new Soldier() { SoldierClass = "Juggernaut", Equipment = new ObservableCollection<Weapon>() { new Weapon("MP5", "Bowie Knife") } }
};
}
}
public class Soldier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _soldierClass;
public string SoldierClass { get { return _soldierClass; } set { _soldierClass = value; OnPropertyChanged("SoldierClass"); } }
private ObservableCollection<Weapon> _equipment;
public ObservableCollection<Weapon> Equipment { get { return _equipment; } set { _equipment = value; OnPropertyChanged("Equipment"); } }
}
public class Weapon : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _primary;
string Primary { get { return _primary; } set { _primary = value; OnPropertyChanged("Primary"); } }
private string _secondary;
string Secondary { get { return _secondary; } set { _secondary = value; OnPropertyChanged("Secondary"); } }
public Weapon(string primary, string secondary)
{
this.Primary = primary;
this.Secondary = secondary;
}
}
}
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
Title="MainWindow" Height="350" Width="580">
<Window.DataContext>
<vm:SquadViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="CbxCharacter" HorizontalAlignment="Left" VerticalAlignment="Top" Width="328" Height="25"
ItemsSource="{Binding SquadMembers}" SelectedItem="{Binding SelectedSoldier}"
DisplayMemberPath="SoldierClass" SelectedValuePath="SoldierClass"/>
<DataGrid x:Name="DgCharacterItems" ItemsSource="{Binding SelectedSoldier.Equipment, Mode=OneWay}" Grid.Column="1" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Primary" Binding="{Binding Primary, Mode=TwoWay}" FontWeight="Bold" Foreground="Black" Width="*" />
<DataGridTextColumn Header="Secondary" Binding="{Binding Secondary, Mode=TwoWay}" Width="*" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
I'm using MVVM for my project and I'm trying to bind a table from my database with a DataGrid. But when I run my application datagrid is empty.
MainWindow.xaml.cs:
public MainWindow(){
InitializeComponent();
DataContext = new LecturerListViewModel()
}
MainWindow.xaml:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Source=Lecturers}" >
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Surname" Binding="{Binding Surname}"/>
<DataGridTextColumn Header="Phone" Binding="{Binding Phone_Number}" />
</DataGrid.Columns>
</DataGrid>
LecturerListViewModel.cs:
public class LecturerListViewModel : ViewModelBase<LecturerListViewModel>
{
public ObservableCollection<Lecturer> Lecturers;
private readonly DataAccess _dataAccess = new DataAccess();
public LecturerListViewModel()
{
Lecturers = GetAllLecturers();
}
and ViewModelBase implements INotifyPropertyChanged.
Lecturer.cs
public class Lecturer
{
public Lecturer(){}
public int Id_Lecturer { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Phone_Number { get; set; }
What did I do wrong? I checked it with debuger and DataContext contains all lecturers, but ther aren't shown in datagrid.
You have an error in binding. Try this:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Lecturers}" >
Code-behind:
private ObservableCollection<Lecturer> _lecturers = new ObservableCollection<Lecturer>();
public ObservableCollection<Lecturer> Lecturers
{
get { return _lecturers; }
set { _lecturers = value; }
}
Here is simple example code (LecturerSimpleBinding.zip).
Here we go
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=Lecturers}" >
Then
private ObservableCollection<Lecturer> lecturers;
public ObservableCollection<Lecturer> Lecturers
{
get { return lecturers; }
set
{
lecturers = value;
this.NotifyPropertyChanged("Lecturers");
}
}
Sayed Saad above is correct. I see two potential problems with your setup, both of which Sayed resolves.
The example posted in the question doen not implement INotifyPropertyChanged
The CLR property being bound to must be a PUBLIC PROPERTY. Fields will not work, as databindindg works via reflection.
Lecturers is a field, but data binding works with properties only. Try declaring Lecturers like:
public ObservableCollection<Lecturer> Lecturers { get; set; }
MainWindow.xaml.cs: OK
MainWindow.xaml: OK
LecturerListViewModel.cs: OK - assuming that GetAllLecturers() method returns an ObservableCollection of Lecturer.
Lecturer.cs:
public class Lecturer : INotifyPropertyChanged
{
//public Lecturer(){} <- not necessary
private int _id;
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
// continue doing the above property change to all the properties you want your UI to notice their changes.
...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Check this answer:
Adding INotifyPropertyChanged to Model?
I'm new to linq, wpf and C#. I've managed to research my way to a functional component. I have attacked data binding successfully but I'm struggling with performance. I'm reading in a static external xml file (i.e. database) and want to display it to users using a wpf datagrid. The additional bit of info is that I'm using a user-controlled wpf combobox to filter how much data from the database is shown in the grid. I'd like to use linq to accomplish this task but I can't seem to get it to perform correctly.
C# file:
namespace ReadPipeXMLDB
{
public partial class ReadDB : Window, INotifyPropertyChanged
{
private XDocument xmlDoc = null;
const string ALL = "All";
// Constructor
public ReadDB()
{
InitializeComponent();
// Load xml
xmlDoc = XDocument.Load("DataBase.xml");
this.DataContext = this;
}
private ObservableCollection<CPipeData> _col;
public ObservableCollection<CPipeData> Col
{
get { return _col; }
set
{
if (_col == value)
return;
_col = value;
OnPropertyChanged(() => Col);
}
}
private ObservableCollection<CMfgData> _mfgCollection;
public ObservableCollection<CMfgData> MfgCollection
{
get { return _mfgCollection; }
set
{
if (_mfgCollection == value)
return;
_mfgCollection = value;
OnPropertyChanged(() => MfgCollection);
}
}
private ObservableCollection<string> _mfgNames;
public ObservableCollection<string> MfgNames
{
get { return this._mfgNames; }
set
{
if (this._mfgNames == value)
return;
this._mfgNames = value;
OnPropertyChanged(() => MfgNames);
}
}
#region Notify Event Declaration and Definition
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged<T>(Expression<Func<T>> property)
{
PropertyChangedEventHandler eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
var memberExpression = property.Body as MemberExpression;
eventHandler(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
}
}
#endregion
public class CMfgData : ReadDB
{
private string _mfgName;
//private ObservableCollection<CPipeData> _pipeDataCollection;
/*public CMfgData(string mfgName, CPipeData pipeData)
{
_mfgName = mfgName;
_pipeData = pipeData;
}*/
#region CMfgData Property Definitions
public string MfgName
{
get { return _mfgName; }
set
{
if (_mfgName == value)
return;
_mfgName = value;
OnPropertyChanged(() => MfgName);
}
}
/* public ObservableCollection<CPipeData> PipeDataCollection
{
get { return _pipeDataCollection; }
set
{
if (_pipeDataCollection == value)
return;
_pipeDataCollection = value;
OnPropertyChanged(() => PipeDataCollection);
}
}*/
#endregion
}
public class CPipeData : ReadDB
{
// PipeData Property Declarations
private string _nominal;
private string _sched;
private string _id;
private string _od;
private string _wt;
public CPipeData()
{
_nominal = "";
_sched = "";
_id = "";
_od = "";
_wt = "";
}
// Constructor
public CPipeData(string nominal, string sched, string id, string od, string wt)
{
_nominal = nominal;
_sched = sched;
_id = id;
_od = od;
_wt = wt;
}
#region CPipeData Property Definitions
public string Nominal
{
get { return _nominal; }
set
{
if (_nominal == value)
return;
_nominal = value;
OnPropertyChanged(() => Nominal);
}
}
public string Sched
{
get { return _sched; }
set
{
if (_sched == value)
return;
_sched = value;
OnPropertyChanged(() => Sched);
}
}
public string ID
{
get { return _id; }
set
{
if (_id == value)
return;
_id = value;
OnPropertyChanged(() => ID);
}
}
public string OD
{
get { return _od; }
set
{
if (_od == value)
return;
_od = value;
OnPropertyChanged(() => OD);
}
}
public string WT
{
get { return _wt; }
set
{
if (_wt == value)
return;
_wt = value;
OnPropertyChanged(() => WT);
}
}
#endregion
}
private void mfgrComboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Update database grid
if (mfgrComboBox1.SelectedValue is string)
{
PopulateGrid(mfgrComboBox1.SelectedValue as string);
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
/*MfgCollection = new ObservableCollection<CMfgData>(
from mfg in xmlDoc.Root.Elements("Mfg")
//where mfg.Attribute("name").Value == comboValue
select new CMfgData
{
MfgName = mfg.Attribute("name").Value,
PipeDataCollection =
new ObservableCollection<CPipeData>
(from pipe in mfg.Elements("pipe")
select new CPipeData
{
Nominal = pipe.Element("Nominal").Value,
Sched = pipe.Element("Schedule").Value,
ID = pipe.Element("ID").Value,
OD = pipe.Element("OD").Value,
WT = pipe.Element("Wall_Thickness").Value
})
});*/
}
private void mfgrComboBox1_Loaded(object sender, RoutedEventArgs e)
{
// Make sure xml document has been loaded
if (xmlDoc != null)
{
ObservableCollection<string> tempCollection = new ObservableCollection<string>(
from n in xmlDoc.Root.Elements("Mfg").Attributes("name")
select n.Value);
// Add the additional "All" filter
tempCollection.Insert(0, ALL);
// Assign list to member property. This is done last so the property event gets fired only once
MfgNames = tempCollection;
PopulateGrid(ALL);
}
}
private void PopulateGrid(string comboValue)
{
if (mfgrComboBox1.Items.IndexOf(comboValue) > -1)
{
Col = new ObservableCollection<CPipeData>(
from mfg in xmlDoc.Root.Elements("Mfg")
where mfg.Attribute("name").Value == comboValue
from pipe in mfg.Elements("pipe")
select new CPipeData
{
Nominal = pipe.Element("Nominal").Value,
Sched = pipe.Element("Schedule").Value,
ID = pipe.Element("ID").Value,
OD = pipe.Element("OD").Value,
WT = pipe.Element("Wall_Thickness").Value
});
}
}
}
}
Xaml:
<Window x:Class="ReadPipeXMLDB.ReadDB"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Standard Pipe Sizes" Height="849" Width="949" Loaded="Window_Loaded">
<Grid>
<!-- Manufactuer filter combobox -->
<ComboBox Name="mfgrComboBox1"
ItemsSource="{Binding Path=MfgNames}"
SelectedIndex="0"
Height="23" Width="286"
HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="20,20,0,0" SelectionChanged="mfgrComboBox1_SelectionChanged" Loaded="mfgrComboBox1_Loaded" />
<!-- Units combobox -->
<ComboBox Height="23" HorizontalAlignment="Left" Margin="320,20,0,0" Name="dimensionsComboBox2" VerticalAlignment="Top" Width="87" />
<!-- Pipe database display grid -->
<DataGrid Name="dataGrid1" IsReadOnly="True" AutoGenerateColumns="False" Margin="20,60,20,20" ItemsSource="{Binding Col}">
<DataGrid.Columns>
<DataGridTextColumn Header="Nominal" Binding="{Binding Path=Nominal}"/>
<DataGridTextColumn Header="Schedule" Binding="{Binding Path=Sched}"/>
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID}"/>
<DataGridTextColumn Header="OD" Binding="{Binding Path=OD}"/>
<DataGridTextColumn Header="Wall Thickness" Binding="{Binding Path=WT}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
XML:
<DBRoot>
<Mfg name="A Manufac">
<pipe>
<Nominal>testdata</Nominal>
<Schedule>testdata</Schedule>
<OD>testdata</OD>
<Wall_Thickness>testdata</Wall_Thickness>
<ID>testdata</ID>
</pipe>
<pipe>
<Nominal>testdata</Nominal>
<Schedule>testdata</Schedule>
<OD>testdata</OD>
<Wall_Thickness>testdata</Wall_Thickness>
<ID>testdata</ID>
</pipe>
</Mfg>
<Mfg name="B Manufac">
<pipe>
<Nominal>testdata</Nominal>
<Schedule>testdata</Schedule>
<OD>testdata</OD>
<Wall_Thickness>testdata</Wall_Thickness>
<ID>testdata</ID>
</pipe>
<pipe>
<Nominal>testdata</Nominal>
<Schedule>testdata</Schedule>
<OD>testdata</OD>
<Wall_Thickness>testdata</Wall_Thickness>
<ID>testdata</ID>
</pipe>
</Mfg>
</DBRoot>
The PopulateGrid call is slow because I create a new ObservableCollection every time the combobox changes value. I'm not use to working with collections and linq so if someone can offer my a more robust alternative I'd appreciate it!
You can save yourself some trouble by binding directly to the XML file:
XAML
<Grid>
<Grid.DataContext>
<XmlDataProvider Source="DataBase.xml"/>
</Grid.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding XPath=/DBRoot/Mfg}" Name="comboBox" SelectedIndex="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding XPath=#name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<DataGrid ItemsSource="{Binding ElementName=comboBox, Path=SelectedItem}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding XPath=Nominal}" Header="Nominal"/>
<DataGridTextColumn Binding="{Binding XPath=Schedule}" Header="Schedule"/>
<DataGridTextColumn Binding="{Binding XPath=OD}" Header="OD"/>
<DataGridTextColumn Binding="{Binding XPath=Wall_Thickness}" Header="Wall Thickness"/>
<DataGridTextColumn Binding="{Binding XPath=ID}" Header="ID"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Grid>
Oh my goodness. Your problem starts with ObservableCollection<string> MfgNames. String has no intelligence and you are building data from scratch every time. Use a class.
public class Mfg
{
public string Name { get; private set; }
public ObservableCollection <CPipeData> pipes { ....
Then in the detail you just bind
ItemsSounce="{binding ElementName=cbMfg Path=SelectedItem.Pipes}"
Look up Master Detail on MSDN.Microsoft.Com
If several Mfg use the same pipe then you would create a hashset with the relationship and pass the hashset to Mfg and use LINQ the filter from that single hashset. Override GetHash.