Binding TextBlock to listBox Item in silveright using MVVM - silverlight

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

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

WPF DataGrid ContentControll bindng

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.

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 adding stack panels from view

I'm not sure my title is clear (poor wpf skills).
What i'm trying to do is to create a smart data entry form. My goal is to have a hard coded data that the user should enter, and on demand (a plus button) he can enter another set of data, every time the user will click the plus button another set will appear in the window (endless)
Edit:
For more details, for a very simple example of what i'm trying to achieve, lets say that this is the window:
And after the user will click the plus button the window will look like this:
And the plus button will always let the user adding more peoples.
Seems like all you need is a List and a ItemControl:
Your Model:
public class User
{
public String Name { get; set; }
public int Age { get; set; }
}
In your ViewModel:
public List<User> Users { get; set; }
//In your constructor
Users = new List<User>();
In your View:
<ItemsControl ItemsSource={Binding Users}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And then below this wire up your add button to a command to point to a method that would do someething like:
private void AddUser()
{
Users.Add(new User());
NotifyPropertyChange("Users");
}
Use an ItemsControl with its ItemsSource property bound to a ReadOnlyObservableCollection<Person>, where Person is a class holding the name and age as strings.
(1) Create Person
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
(2) Create PeopleViewModel, holding your collection.
public class PeopleViewModel
{
private ObservableCollection<Person> _people;
public ReadOnlyObservableCollection<Person> People { get; private set; }
public PeopleViewModel()
{
_people = new ObservableCollection<Person>();
People = new ReadOnlyObservableCollection<Person>(_people);
addPerson(); // adding the 1st person
}
// You also need to hook this up to the button press somehow
private void addPerson()
{
_people.Add(new Person());
}
}
(3) Set the DataContext of your window to be a PersonViewModel in the code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new PeopleViewModel();
}
}
(4) Create an ItemsControl along with a DataTemplate for Person
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Don't forget to hook up your button either through a Command or through the Button.Click event.

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

Resources