I'm trying to do a picker that loads ItemSource from a List and depending on an external event, change its SelectedIndex based on Local.id, but what I've been trying so far didn't works.
C# code:
public class Local
{
public string cidade { get; set; }
public int id { get; set; }
}
public int CidadeSelectedIndex{ get; set; }
string jsonCidades;
public async void CarregaCidades()
{
try
{
using (WebClient browser = new WebClient())
{
Uri uriCidades = new Uri("xxxxxxx.php");
jsonCidades = await browser.DownloadStringTaskAsync(uriCidades);
}
var ListaCidades = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Local>>(jsonCidades);
PickerCidades.ItemsSource = ListaCidades;
}
catch (Exception)
{
throw;
}
}
//In some moment of the execution, this code is called:
Local localizacao = JsonConvert.DeserializeObject<Local>(json);
if (localizacao.GetType().GetProperty("id") != null)
{
/*CidadeSelectedItem = localizacao;
I tried that before with SelectedItem="{Binding CidadeSelectedItem, Mode=TwoWay}" */
CidadeSelectedIndex = localizacao.id; // now trying this
}
Before I was trying to bind using ItemDisplayBinding="{Binding ListaCidades.cidade, Mode=OneWay}" but since it was not working I start to use ItemSources=ListaCidades
My XAML code:
<Picker x:Name="PickerCidades"
SelectedIndex="{Binding CidadeSelectedIndex, Mode=TwoWay}"
Grid.Column="1" Grid.Row="0"
SelectedIndexChanged="PickerCidades_SelectedIndexChanged">
</Picker>
I think it's not working because I'm setting the items using ItemsSource. I think I need to bind it using xaml. Would be nice have some help.
Do you want to achieve the result like following GIF?
My xaml layout like following code.
<StackLayout>
<!-- Place new controls here -->
<Picker x:Name="PickerCidades"
ItemsSource="{ Binding locals}"
SelectedIndex="{Binding CidadeSelectedIndex, Mode=TwoWay}"
ItemDisplayBinding="{Binding cidade}"
Grid.Column="1" Grid.Row="0"
SelectedIndexChanged="PickerCidades_SelectedIndexChanged">
</Picker>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="CidadeSelectedIndex: " Grid.Column="0" Grid.Row="0"/>
<Label Text="{Binding CidadeSelectedIndex}" Grid.Column="1" Grid.Row="0"/>
</Grid>
</StackLayout>
Layout background code.
public partial class MainPage : ContentPage
{
MyViewModel myViewModel;
public MainPage()
{
InitializeComponent();
myViewModel= new MyViewModel();
BindingContext = myViewModel;
}
private void PickerCidades_SelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
myViewModel.CidadeSelectedIndex = selectedIndex;
}
}
MyViewMode code.I use static data for testing. You can achieve the INotifyPropertyChanged interface to change dynamically.
public class MyViewModel : INotifyPropertyChanged
{
int _cidadeSelectedIndex=1;
public int CidadeSelectedIndex
{
set
{
if (_cidadeSelectedIndex != value)
{
_cidadeSelectedIndex = value;
OnPropertyChanged("CidadeSelectedIndex");
}
}
get
{
return _cidadeSelectedIndex;
}
}
public ObservableCollection<Local> locals { get; set; }
public MyViewModel()
{
locals = new ObservableCollection<Local>();
locals.Add(new Local() { cidade= "xxx0" , id= 0 });
locals.Add(new Local() { cidade = "xxx1", id = 1 });
locals.Add(new Local() { cidade = "xxx2", id = 2 });
locals.Add(new Local() { cidade = "xxx3", id = 3 });
locals.Add(new Local() { cidade = "xxx4", id = 4 });
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If the goal is to change the User Interface from code you need to have a ViewModel that implements INotifyPropertyChanged (or inherits from a base that does). Then instead of SelectedIndex bound property being a simple get; set as below it fires off the PropertyChanged event.
public int CidadeSelectedIndex{ get; set; }
Needs to fire notification event. Something along these lines
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _cidadeSelectedIndex;
public int CidadeSelectedIndex
{
get => _cidadeSelectedIndex;
set {
_cidadeSelectedIndex = value;
NotifyPropertyChanged();
}
}
}
Related
I am working on an iOS App, written in C#, Xamarin. I use a Picker in MVVM Architecture and want to change the pickers title.
but when i change the Pickers Title with OnAddMaterial, the Title doesnt change.
The ViewModel:
private void OnAddNewMaterial()
{
SelectedMaterialIndex = -1;
MaterialPickerTitle = "New Material";
}
private string _materialPickerTitle { get; set; }
public string MaterialPickerTitle
{
get { return _materialPickerTitle; }
set
{
_materialPickerTitle = value;
OnPropertyChanged();
}
}
The View:
<Picker Title="{Binding MaterialPickerTitle}" Margin="12,4,4,4" Grid.Row="0" Grid.Column="0" HorizontalOptions="FillAndExpand" ItemsSource="{Binding Materials}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding SelectedMaterial}" SelectedIndex="{Binding SelectedMaterialIndex}" />
I use Visual Studio 2019.
EDIT:
when i am initializing the view, i set the title from the Picker. that works great. After that, i am assigning Objects to the ItemSource from Picker. When i am trying to set the pickers title after that it doesnt works.
I wrote a demo and the title of Picker can be changed after I change the selectedItem. Here is the code you can refer:
public partial class MainPage : ContentPage
{
List<string> monkeyList = new List<string>();
TestModel model = new TestModel();
public MainPage()
{
InitializeComponent();
monkeyList.Add("Baboon");
monkeyList.Add("Capuchin Monkey");
monkeyList.Add("Blue Monkey");
monkeyList.Add("Squirrel Monkey");
monkeyList.Add("Golden Lion Tamarin");
monkeyList.Add("Howler Monkey");
monkeyList.Add("Japanese Macaque");
picker.ItemsSource = monkeyList;
model.MaterialPickerTitle = "123";
model.SelectedMaterialIndex = 2;
BindingContext = model;
}
private void Button_Clicked(object sender, EventArgs e)
{
monkeyList.Add("Baboonww");
model.SelectedMaterialIndex = -1;
model.MaterialPickerTitle = "456";
}
}
class TestModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public TestModel()
{
}
string materialPickerTitle;
public string MaterialPickerTitle
{
set
{
if (materialPickerTitle != value)
{
materialPickerTitle = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("MaterialPickerTitle"));
}
}
}
get
{
return materialPickerTitle;
}
}
int selectedMaterialIndex;
public int SelectedMaterialIndex
{
set
{
if (selectedMaterialIndex != value)
{
selectedMaterialIndex = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedMaterialIndex"));
}
}
}
get
{
return selectedMaterialIndex;
}
}
}
And in xaml:
<StackLayout>
<!-- Place new controls here -->
<Button Clicked="Button_Clicked" Text="click to change title"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Picker x:Name="picker"
Title="{Binding MaterialPickerTitle}"
SelectedIndex="{Binding SelectedMaterialIndex}"
TitleColor="Red">
</Picker>
</StackLayout>
Please check your bindings in your project. Add some breakPoints to debug if the title changes. I also upload my sample project here.
So I have a user control:
<StackPanel Orientation="Vertical"
Margin="10">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Stretch"
Margin="10">
<TextBlock Text="{x:Bind FileName, Mode=OneTime}"
HorizontalAlignment="Left"/>
<TextBlock Text="{x:Bind DownloadSpeed, Mode=OneWay}"
HorizontalAlignment="Right"/>
</StackPanel>
<ProgressBar Name="PbDownload"
HorizontalAlignment="Stretch" />
<TextBlock Text="{x:Bind DownloadCompletePercent, Mode=OneWay}"/>
</StackPanel>
User control code behind:
public sealed partial class UCDownloadCard : UserControl
{
public UCDownloadCard()
{
this.InitializeComponent();
}
public string FileName { get; set; }
public string DownloadSpeed { get; set; }
public string DownloadCompletePercent { get; set; }
}
What I am trying to do is to show file download status using this user control. Whenever a new download is started, I want to programmatically add a new user control and then update the values in it as the download happens.
Currently I am doing something like this:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public CancellationTokenSource CancellationTokenSource { get; set; }
public List<DownloadOperation> ActiveDownloads { get; set; } = new List<DownloadOperation>();
public List<UCDownloadCard> AddedCards { get; set; } = new List<UCDownloadCard>();
private async Task HandleDownloadAsync(DownloadOperation downloadOperation, CancellationToken cancellationToken = new CancellationToken())
{
ActiveDownloads.Add(downloadOperation);
...
...
try
{
AddDownloadProgressCard();
await downloadOperation.StartAsync().AsTask(CancellationTokenSource.Token, progressCallback);
}
finally
{
...
...
}
}
private void AddDownloadProgressCard()
{
var card = new UCDownloadCard
{
Name = $"Card{AddedCards.Count}",
FileName = "Filename.pdf",
DownloadCompletePercent = "0% completed",
DownloadSpeed = "0 KB/s"
};
AddedCards.Add(card);
OutputArea.Children.Add(card);
}
private void DownloadProgressChanged(DownloadOperation downloadOperation)
{
var downloadPercent = 100 * ((double)downloadOperation.Progress.BytesReceived / (double)downloadOperation.Progress.TotalBytesToReceive);
this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
AddedCards[0].DownloadCompletePercent = downloadPercent.ToString();
Debug.WriteLine($"Updating Progress: {downloadPercent}%");
});
}
}
I am able to add the UserControl to the OutputArea but the values in it are not updating. But I am sure that the AddedCards[0].DownloadCompletePercent = downloadPercent.ToString(); is being executed multiple times because the Debug.WritLine just below it is actually printing to the output window.
How can I update the values in the UserControl ?
Firstly, you should change your UserControl with x:Bind Mode=TwoWay.See {x:Bind} markup extension for more details.
Then you should implement the INotifyPropertyChanged interface and implement the PropertyChanged event. The code you can refer the PropertyChanged event.
Here is a simple sample, you can have a reference.
UserControl.xaml,
<StackPanel Orientation="Vertical"
Margin="10">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Stretch"
Margin="10">
<TextBlock Text="{x:Bind DownloadCompletePercent, Mode=TwoWay}"/>
</StackPanel>
UserControl.xaml.cs,
public sealed partial class UCDownloadCard : UserControl, INotifyPropertyChanged
{
public UCDownloadCard()
{
this.InitializeComponent();
}
private string downloadCompletePercent;
public string DownloadCompletePercent
{
get
{
return downloadCompletePercent;
}
set
{
downloadCompletePercent = value;
RaisePropertyChanged("DownloadCompletePercent");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Then you can add this UserControl and update its downloadCompletePercent,
In the MainPage.xaml.cs,
private void DownloadProgress(DownloadOperation obj)
{
BackgroundDownloadProgress currentProgress = obj.Progress;
double percent;
if (currentProgress.TotalBytesToReceive > 0)
{
percent = currentProgress.BytesReceived * 100 / currentProgress.TotalBytesToReceive;
Debug.WriteLine(percent);
uCDownloadCard.DownloadCompletePercent = percent.ToString();
}
}
UCDownloadCard uCDownloadCard;
private void Button_Click_2(object sender, RoutedEventArgs e)
{
uCDownloadCard = new UCDownloadCard();
MainPagePanel.Children.Add(uCDownloadCard);
}
I have below xaml file (this a piece):
<Grid Opacity="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="ID"/>
<Label Grid.Row="0" Grid.Column="1" Content="Name"/>
<Label Grid.Row="0" Grid.Column="2" Content="Description"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding ID}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Description}"/>
</Grid>
Below the Data Class:
public class Data : INotifyPropertyChanged
{
private string id= string.Empty;
private string name = string.Empty;
private string description = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string ID
{
get
{
return this.id;
}
set
{
if (value != this.id)
{
this.id = value;
NotifyPropertyChanged("ID");
}
}
}
public string Name
{
get
{
return this.name;
}
set
{
if (value != this.name)
{
this.name = value;
NotifyPropertyChanged("Name");
}
}
}
public string Description
{
get
{
return this.description;
}
set
{
if (value != this.description)
{
this.description = value;
NotifyPropertyChanged("Description");
}
}
}
}
Also in xaml.cs I implement INotifyPropertyChanged:
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
Furthermore, in the above xaml I have a button defined as:
<Button Click="btn_Click"/>
and the implementation for that is in xaml.cs as below:
private void btn_Click(object sender, System.Windows.RoutedEventArgs e)
{
(DataContext as MyViewModel).SearchInDb('0303003'); // 0303003 -> this is only an example.
}
On button click a method on MyViewModel class is called, and from there it invokes a query to database to retrieve data using ID = 0303003.
Below MyViewModel class (I show only the method):
public void SearchInDb(string id)
{
// request data to sql server database
// and then populate a Data Class Object with the data retrieved
// from database:
Data = new Data(){
ID = sReader[0].ToString().Trim(),
Name = sReader[1].ToString().Trim(),
Description = sReader[2].ToString().Trim()
};
}
Note: MyViewModel class does not implement INotifyPropertyChanged.
My problem is the following:
After populating a new Data object within above method "SearchInDb", my labels in the grid are not updated, they remain empty.
You need to set the View's DataContext:
View.xaml.cs
public View()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
But there is some other issues in your snippets. In the MVVM way, it is the ViewModel which is supposed to implements INotifyPropertyChanged. Not the data class.
Here is how it is supposed to be:
Data Class
public class Data
{
public string Id {get;set;}
public string Name {get;set;}
public string Description {get; set;}
}
MyViewModel
public class MyViewModel : INotifyPropertyChanged
{
private Data _data;
public string ID
{
get { return _data.Id;}
set
{
if(_data.Id != value)
{
_data.Id = value;
NotifyPropertyChanged();
}
}
}
// Same stuff for the other properties
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void SearchInDb(string id)
{
// request data to sql server database
// and then populate a Data Class Object with the data retrieved
// from database:
_data = new Data()
{
Id = sReader[0].ToString().Trim(),
Name = sReader[1].ToString().Trim(),
Description = sReader[2].ToString().Trim()
};
NotifyPropertyChanged(nameof(ID));
NotifyPropertyChanged(nameof(Name));
NotifyPropertyChanged(nameof(Description));
}
}
There was nothing wrong with your NotifyPropertyChanged code. It is just an old way of doing it. This way is more modern and does not require magic strings ;-).
You can also bind the Command dependency property of your button to your SearchInDb methods by using a Command property in you view model. This way, you do not need to write code in your code behind. But that's another question :D.
And there is no need for your View to implements INotifyPropertyChanged (unless your case specifically required this).
I have a DevExpress GridControl in WPF with a bound ItemsSource and fields in the columns. When I initialise the values in the data source, everything works fine, but when the data is supposed to update, it doesn't.
I also have a label in the user control which contains the GridControl and that updates fine.
So my XAML is:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="250" />
<RowDefinition Height="3*" />
</Grid.RowDefinitions>
<dxg:GridControl Grid.Row="0" x:Name="grid" DataContext="{StaticResource ParamDataSource}" ItemsSource="{Binding Path=ObservableParams}">
<dxg:GridControl.Columns>
<dxg:GridColumn x:Name="ParamName" FieldName="ParamName" MinWidth="80" Width="80" AllowResizing="False" FixedWidth="True" Header="Parameter" />
<dxg:GridColumn x:Name="ParamValue" Binding="{Binding ParamValue}" MinWidth="50" Width="50" SortIndex="0" Header="Best Value" />
</dxg:GridControl.Columns>
<dxg:GridControl.View>
<dxg:TableView VerticalScrollbarVisibility="Hidden" x:Name="view" ShowGroupPanel="False" AllowFixedGroups="True" ShowGroupedColumns="False" AllowCascadeUpdate="False" AllowScrollAnimation="False" NavigationStyle="Row" AutoWidth="True" ShowFixedTotalSummary="False" />
</dxg:GridControl.View>
</dxg:GridControl>
<Label DataContext="{StaticResource ParamDataSource}" Content="{Binding LabelText}" Margin="10, 10, 10, 10" Grid.Row="1"/>
</Grid>
And then the c# code for the data source...
class ParamDataSource : ViewModelBase // using DevExpress.Mvvm above
{
public ParamDataSource()
{
// This stuff is put on the grid no problem.
ObservableParams = new System.Collections.ObjectModel.ObservableCollection<ParamTableRow>
{
new ParamTableRow
{
ParamName = "Param1",
ParamValue = 0
},
new ParamTableRow
{
ParamName = "Param2",
ParamValue = 0
},
new ParamTableRow
{
ParamName = "Param3",
BestValue = 0
}
};
LabelText = "Starting Now";
}
public ObservableCollection<ParamTableRow> ObservableParams { get; set; }
public string LabelText { get; set; }
public void UpdateParam(int paramIndex, decimal? paramValue)
{
ObservableParams[paramIndex].ParamValue = paramValue;
RaisePropertyChanged("ObservableParams");
// This label updates on the view just fine, but not the parameter values...
LabelText = string.Format("Done Param {0}", paramIndex);
RaisePropertyChanged("LabelText");
}
}
public class ParamTableRow
{
public string ParamName { get; set; }
public decimal? ParamValue { get; set; }
}
Just implement INotifyPropertyChanged on your model class:
public class ParamTableRow:INotifyPropertyChanged
{
private string paramName;
public string ParamName
{
get { return paramName; }
set {
paramName = value;
OnPropertyChanged("ParamName");
}
}
private decimal? paramValue;
public decimal? ParamValue
{
get { return paramValue; }
set
{
paramValue = value;
OnPropertyChanged("ParamValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Cause ObservableCollection implements INotifyCollectionChanged, not INotifyPropertyChanged.
INotifyCollectionChanged is used to notify UI when items are added or remove from collection.
INotifyPropertyChanged is used to notify UI when new value is set to your property.
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>