WPF DataGrid ContentControll bindng - wpf

I want to make DataGrid with a structure as in image below.
To have two TextBoxes in each cell.
Ive made Class
public class ComplexTable : ViewModelBase
{
public ComplexTable()
{
FirstProperty = new FirstClass();
SecondProperty = new Second();
}
public class FirstClass
{
public FirstClass()
{
First = "FirstString";
Second = "SecondString";
}
public string First { get; set; }
public string Second { get; set; }
}
public class Second
{
public Second()
{
Third = "ThirdString";
Fourth = "FourthString";
}
public string Third { get; set; }
public string Fourth { get; set; }
}
public FirstClass FirstProperty { get; set; }
public Second SecondProperty { get; set; }
}
public ObservableCollection<ComplexTable> _testCollection = new ObservableCollection<ComplexTable>();
private ObservableCollection<ComplexTable> TestCollection
{
get { return _testCollection; }
set
{
_testCollection = value;
RaisePropertyChanged("TestCollection");
}
}
And a TestCollection that should be a ItemsSource for DataGrid.
My DataGrid
<DataGrid CanUserAddRows="True"
ItemsSource="{Binding TestCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text=" "/>
<TextBox Text=" "/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Second Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text=" "/>
<TextBox Text=" "/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I can't figure out how to bind those textboxes. Or i went in a wrong direction?

The datagrid sets the DataContext of the columns to the elements of the ItemSource. Every element in that ItemSource will be displayed as one Row. In your case the ItemSource is TestCollection. Therefore the DataContext inside your DataGridTemplateColumn is set to the elements of the TestCollection. If TestCollection contains ComplexTable elements. You can bind directly to the properties on ComplexTable.
var TestCollection = new ObservableCollection<DataForOneRow> {DataForFirstRow, DataForSecondRow, DataForThirdRow};
public class DataForOneRow {
public string DataForFirstColumnFirstTextBox {get; set;} //left out raise of PropertyChanged for brevity
public string DataForFirstColumnSecondTextBox {get; set;}
public string DataForSecondColumnFirstTextBox {get; set;}
public string DataForSecondColumnSecondTextBox {get; set;}
}
<DataGridTemplateColumn Header="First Column">
<DataGridTemplateColumn.CellEditingTemplate >
<DataTemplate >
<ContentControl>
<StackPanel>
<TextBox Text="{Bidning DataForFirstColumnFirstTextBox}"/>
<TextBox Text="{Bidning DataForFirstColumnSecondTextBox}"/>
</StackPanel>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
FYI: You have to raise a PropertyChanged event in all ViewModel properties.

Related

WPF: Access SelectedItem of nested DataGrid

I have a nested DataGrid setup. How do I access the SelectedItem for the inner grid?
<DataGrid ItemsSource="{Binding OuterGrid}" RowDetailsVisibilityMode="Visible">
<DataGrid.Columns>
<DataGridCheckBoxColumn ..... />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding InnerGrid}" SelectedItem="{Binding SelectedInner}">
<DataGridTextColumn ..... />
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
SelectedInner is a property and even if I select a row in the InnerGrid in the UI, that property is still null.
How do I know which row was selected in the inner grid? I also use caliburn-micro.
It depends on where the 'SelectedInner' object is in your ViewModel. If the the object is in your ViewModel of your view (as is in the example below), you need to use RelativeResource in order to point out the datagrid where your bound object is.
class ShellViewModel:Screen
{
public BindableCollection<DataGridObject> OuterGrid { get; set; } = new BindableCollection<DataGridObject>();
public BindableCollection<DataGridObject> InnerGrid { get; set; } = new BindableCollection<DataGridObject>();
public DataGridObject selectedInner { get; set; } = new DataGridObject();
public ShellViewModel()
{
OuterGrid.Add(new DataGridObject {String1="water",String2="air",String3="earth" });
OuterGrid.Add(new DataGridObject {String1="water1",String2="air2",String3="earth2" });
OuterGrid.Add(new DataGridObject { String1 = "water3", String2 = "air3", String3 = "earth3" });
InnerGrid.Add(new DataGridObject {String1="water",String2="air",String3="earth" });
InnerGrid.Add(new DataGridObject {String1="water1",String2="air2",String3="earth2" });
InnerGrid.Add(new DataGridObject {String1="water3",String2="air3",String3="earth3" });
NotifyOfPropertyChange(() => OuterGrid);
NotifyOfPropertyChange(() => InnerGrid);
}
}
public class DataGridObject
{
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
}
On the xaml you need to use RelativeResource
<DataGrid ItemsSource="{Binding OuterGrid}" RowDetailsVisibilityMode="Visible">
<!--<DataGrid.Columns>
<DataGridCheckBoxColumn />
</DataGrid.Columns>-->
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel>
<DataGrid ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},Path=DataContext.InnerGrid}" SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window},Path=DataContext.selectedInner}" >
</DataGrid>
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Make sure you specify the correct ancestor which ma be window or usercontrol

How to Bind a Dictionary with the Key value being a Binding Property in WPF?

I have a class like this:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int AccountId { get; set; }
public Dictionary<int, List<string>> Values { get; set; }
}
I have a DataGrid in my XAML where I want to display the first index of the List<string> within the dictionary property Values as one of the column values, where the key being passed in will be the AccountId. The ItemSource of my DataGrid is a list of Person objects coming from my ViewModel, and the DataGrid has 3 columns, PersonId, Name, and Value (where value is the first index of the List<string> collection in the dictionary item)
I have seen examples on stackoverflow and other places on the internet that try to do this but none of the solutions worked for me.
Here is my XAML code:
<DataGrid Name="MyDataGrid" ItemsSource="{Binding Persons}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ID">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding PersonId}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Values[{Binding AccountId}][0]}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The last column is the column where I try to use a {Binding} as the key value, but it does not work. If I hard-code a valid AccountId, it works. Has anyone encountered this before?
Thanks!
one of view model purposes is to provide data to view in a convenient format. code to get value from dictionary by key and then return first item can be written in a view model, not in converter:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int AccountId { get; set; }
public Dictionary<int, List<string>> Values { get; set; }
public string AccountIdValue { get { return Values[AccountId].FirstOrDefault(); } }
}
then bind to that helper property:
<TextBox Text="{Binding AccountIdValue}" IsEnabled="False" />

Binding list property to TextBlock

I have a class named MyClass which looks like this:
public class MyClass : BaseObject, IDisposable
{
public int MyClassId { get; set; }
public IList<MyClassTranslation> MyTranslations { get; set; }
}
And MyClassTranslation looks like this:
public class MyClassTranslation
{
public string MyClassName { get; set; }
}
I have a view and a view model. The View model looks like this:
class MyClassViewModel : ViewModelBase, IMyClassViewModel
{
public MyClassViewModel()
{
myObjects = GetMyObjects();
}
public IList<MyClass> myObjects { get; set; }
}
And here's the view:
<ItemsControl ItemsSource="{Binding myObjects}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center">
<TextBlock FontSize="24pt" FontWeight="Bold" TextAlignment="Center" Text="{Binding MyClassTranslation.MyClassName[0]}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The problem is that the myClassName property isn't shown. How do I bind it?
assuming that the data context is set properly, so the proper binding would be
<StackPanel HorizontalAlignment="Center">
<TextBlock FontSize="24pt"
FontWeight="Bold"
TextAlignment="Center"
Text="{Binding MyTranslations[0].MyClassName}">
</TextBlock>
</StackPanel>
you can use indexer to bind such values

WPF TabControl ContentTemplate List

Inexperienced with WPF, so I need a little help. Appreciate the help in advance.
I have the following class:
public class TabDefn
{
public TabDefn() { }
public TabDefn(string inFolderName, List<FilesFolder> inFilesFolders)
{
folderName = inFolderName;
FFs = inFilesFolders;
}
public string folderName { get; set; }
public List<FilesFolder> FFs {get; set;}
}
public class FilesFolder
{
public FilesFolder() {}
//public Image image { get; set; }
public string ffName { get; set; }
//public Image arrow { get; set; }
}
The TabControl.ItemContent is working fine. I can't get anything to show up for the TabControl.ContentTemplate. I've tried many things, but this is where the WPF is right now:
<TabControl Grid.Column="1" Grid.Row="1" Visibility="Hidden" Name="Actions">
<!-- This displays the tab heading perfectly.-->
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding folderName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- This is the content of the tab that I can't get anything to show up in.-->
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding FF}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding ffName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I don't care if the content changes, so I don't need the INotifyPropertyChanged or ObservableCollection. However, if I have to put all that code in, so be it.
You declare FF as field which is invalid binding source. You need to convert it into property
public List<FilesFolders> FF { get; set; }
and initialize it for example in TabDefn constructor. You can find more as to what is a valid binding source on MSDN

Binding TextBlock to listBox Item in silveright using MVVM

I am developing a silverlight navigation application and have encountered the following problem. I am using MVVM to connect a listbox with a class. The class hase a name, some text and a email address.
public class ContactPage
{
public List<ContactInfo> Contacts { get; set; }
public Image Image { get; set; }
public string Description { get; set; }
//some other code not needed
}
public class ContactInfo
{
public string Name { get; set; }
public List<string> Data { get; set; }
public List<Url> Urls { get; set; }
public ContactInfo(string name, List<string> data, List<string> urls )
{
Name = name;
Data = data;
Urls = urls;
}
}
The xaml file that contains the problematic part looks like this
<ListBox ItemsSource="{Binding ContactPage.Contacts, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name, Mode=TwoWay}" FontWeight="Bold"/>
<ListBox x:Name="dataListBox" ItemsSource="{Binding Data, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="???"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox ItemsSource="{Binding Urls, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<HyperlinkButton Content="{Binding Address, Mode=TwoWay}" ClickMode="Press">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding NavigateCommand}"
CommandParameter="{Binding Action, Mode=TwoWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</HyperlinkButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have now two questions.
I am trying to bind the listbox to Data which is a list of string. Each of this elements i want in a separated textblock... To which property do I have to bind this texblock so that it shows the right data
<ListBox x:Name="dataListBox" ItemsSource="{Binding Data, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="???"/> <!--What to put here???-->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
How can i make Hyperlink buttons clickable. I have set up all in the viewmodel but after i click the link nothing happens. I guess it's because the button is a list item but am not sure how to solve it.
Hope that anyone can help me with at least one problem...
Edit:
Thanks for the answers... the first one works great but the second doesnt... i have just the same commanding as on the site you mentiond. Here is what I did but it's not working:
public ICommand NavigateCommand
{
get { return new RelayCommand<object>(param => Navigate(param), param => true); }
}
private void Navigate (object parameter)
{
Url url = parameter as Url;
if (url.Action.StartsWith("http"))
{
HtmlPage.Window.Navigate(new Uri(url.Action, UriKind.Absolute), "_blank");
}
else if (url.Action.StartsWith("mailto"))
{
HtmlPage.Window.Navigate(new Uri(url.Action, UriKind.Absolute));
}
}
this is the actual url class just to have all clear
public class Url
{
public string Address { get; set; }
public string Action { get; set; }
public Url(string address, string action)
{
Address = address;
Action = action;
}
}
and the binding looks like this now
<ListBox Name="linkListBox" ItemsSource="{Binding Urls, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<HyperlinkButton Content="{Binding Address, Mode=TwoWay}" ClickMode="Press">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding NavigateCommand}"
CommandParameter="{Binding ElementName=linkListBox, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</HyperlinkButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It isnt eveing firing the NavigateCommand in debug mode...
create a new class instead of list of strings in contact info
public DataClass
{
public string DataName { get; set; }
public DataClass(string dataName)
{
DataName = dataName;
}
}
change the list of string property to list of DataClass
public class ContactInfo
{
public string Name { get; set; }
public List<Dataclass> Data { get; set; }
public List<Url> Urls { get; set; }
public ContactInfo(string name, List<string> data, List<string> urls )
{
Name = name;
Urls = urls;
var objDataClass = ne List<Dataclass>();
foreach(string str in data)
{
objDataClass.Add(new Dataclass(str));
}
Data = objDataClass;
}
}
now you can bind the Textblock with a property from Dataclass called "DataName"
<ListBox x:Name="dataListBox" ItemsSource="{Binding Data, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
1) Text="{Binding}"/>
2) create new type with properties.String DisplayAddress ,String Address, ICommand NavigateCommand; , please see this link for craeting command
http://www.silverlightshow.net/items/Silverlight-4-How-to-Command-Control.aspx

Resources