DataBinding to listbox - c#

The "a.text" is extracted from my JSON Response. I am trying to display the "a.text" in the listbox.
List<Feature> features = App.dResult.directions[0].features;
foreach (Feature f in features)
{
Attributes a = f.attributes;
MessageBox.Show(a.text);
directionListBox.ItemsSource = a.text;
}
I tried using binding the "a.text" to the listbox but there is no display.
<ListBox x:Name="directionListBox" ItemsSource="{Binding a.text}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding a.text}" Style="{StaticResource PhoneTextTitle2Style}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Can anyone provide me with some idea on how to bind the "a.text" to listbox?
Any help will be greatly appreciated.

The ItemsSource Property of a ListBox should not point to a String, like a.text, but to an ObservableCollection.
Try something like this:
1st: derive that code class from INotifyPropertyChanged.
2nd: You can get the ObservableList from here or use ObservableCollection instead.
3rd: Then use this code (untested, but might work):
ObservableList<String> featList = new ObservableCollection<String>();
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableList<String> FeatList
{
get { return featList; }
set
{
featList = value;
InvokePropertyChanged("FeatList");
}
}
List<Feature> features = App.dResult.directions[0].features;
foreach (Feature f in features)
{
Attributes a = f.attributes;
//MessageBox.Show(a.text);
FeatList.add(a.text);
}
4th: At the top of the fild you have to use a 'using' statement to import the ObservableList.
Then, bind the listbox'es ItemSource to this list, and make the TextBlock's Binding just bind to the default DataContext, which will be the string from the list:
<ListBox x:Name="directionListBox" ItemsSource="{Binding FeatList}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding}" Style="{StaticResource PhoneTextTitle2Style}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

WPF Navigate through child elements or keep track of dynamically created child elements

I have a TabControl where I create tabs dynamically. I am finding it difficult to change the title of the TabItem.
<TabControl Name="AttorneysTabControl" Grid.Column="2" Grid.Row="0">
<TabControl.Resources>
<DataTemplate x:Key="AttorneyTabHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Names}" Margin="2,0,0,0" FontSize="16" VerticalAlignment="Center" />
<Button Width="Auto" UseLayoutRounding="False" BorderBrush="Transparent" Background="Transparent" Click="CloseAttorneysTabButtonClick">
<Image Source="/images/close-cross-thin-circular-button/close-cross-thin-circular-button16.png" Height="16"></Image>
</Button>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="AttorneyTabContent">
<local:AttorneyDetails></local:AttorneyDetails>
</DataTemplate>
</TabControl.Resources>
For each TabItem I set a HeaderTemplate from the TabControl.Resources like this;
newTabItem.HeaderTemplate = (System.Windows.DataTemplate)AttorneysTabControl.FindResource("AttorneyTabHeader");
But I don't know how to change the contents of the TabItem header once the template has been set. I have tried using DataContext for the TabItem if that's the way to do it but it did not work, so that I could just use Binding in the template. That will be a lot easier.
You should normally write (first line is your unchanged code):
newTabItem.HeaderTemplate = (System.Windows.DataTemplate)AttorneysTabControl.FindResource("AttorneyTabHeader");
var tabItemData = new TabItemData() { Name="Initial name"} ;
newTabItem.DataContext = tabItemData;
And then once you need to update the tab header:
tabItemData.Name = "New name".
If that didn't work, that'd probably because your TabItemData.Name property doesn't notify of its changes. So make sure that your TabItemData class implements INotifyPropertyChanged and that the Name property notifies. Example:
public class TabItemData : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return this.name; }
set
{
if (value != this.name)
{
this.name= value;
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In case you're lost I suggest reading the Managing data in a WPF application chapter of my Learn WPF MVVM book.

How to make a datagrid see edits in a control?

I have a user control that I am using to populate a datagrid.
I would like the user to be able to add items by editing the empty row at the bottom. (This is why I am using a datagrid rather than an itemscontrol) However the datagrid does not realise that the last item is edited unless the user clicks the background of the control. I would like the new item to be added when the user makes changes on the properties that the control exposes.
XAML of the control:
<UserControl x:Class="ControlTest.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ControlTest"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="300"
DataContext="{Binding Path=., Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Path=p1, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Width="300"
Height="30"
VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding Path=DropDownValues,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=local:MyControl}}"
SelectedItem="{Binding Path=p2, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Height="30"/>
</StackPanel>
</UserControl>
cs:
public partial class MyControl : UserControl
{
private static readonly DependencyProperty DropDownValuesProperty =
DependencyProperty.Register(
"DropDownValues",
typeof(List<String>),
typeof(MyControl),
new FrameworkPropertyMetadata(new List<String>()
));
public List<String> DropDownValues
{
get
{
return (List<String>)GetValue(DropDownValuesProperty);
}
set
{
SetValue(DropDownValuesProperty, value);
}
}
public MyControl()
{
InitializeComponent();
}
}
DataGrid XAML
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding objs, Mode=TwoWay}"
HeadersVisibility="None"
Margin="0,0,0.4,0"
CanUserAddRows="True"
>
<DataGrid.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</DataGrid.ItemsPanel>
<DataGrid.Columns>
<DataGridTemplateColumn Width="300">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="local:Measure">
<local:MyControl
DataContext="{Binding ., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DropDownValues=
"{Binding DataContext.list, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
Width="300"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Can I make this work, and/or is there a better way to do this?
I would like to suggest you a different way of doing that:
set CanUserAddRows=false on your DataGrid and then manually add rows to the ObservableCollection<Something> to which your DataGrid is bound to.
OR
If you are still interested in the approach that you follow:
In your xaml file:
<DataGrid x:Name="myDataGrid" CellEditEnding="DataGrid_CellEditEnding" .....>
<!--Some Code-->
</DataGrid>
Then in the Code-Behind:
private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
myDataGrid.CommitEdit();
}
If you don't understand anything then feel free to ask.
Update
If you are following the same approach:
In your DataGrid's Beginning edit event you can try:
private void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
if ((selectedRow as DataGridRow).Item.ToString() != "{NewItemPlaceholder}")
{
//Here you can add the code to add new item. I don't know how but you should figure out a way
}
}
Note: The code mentioned above is not tested.
I would also suggest you :
Not to use DataGrid. Instead use ListBox. Because, you are trying to add some data. At this time you never need sorting, searching and column-reordering fascilities. In such scenario, ListBox is useful as it is light-weight control than datagrid. I have a sample here: https://drive.google.com/open?id=0B5WyqSALui0bTXFGZWxQUWVRdkU
Is the problem that the UI is not being notified of changes to the objs collection? What I would do is try setting up whatever view model that contains objs to make objs an observable collection. I would also ensure that whatever objs is an observable collection of implements INotifyPropertyChanged and that properties p1 and p2 both fire OnPorpertyChanged when they are set.
public ObservableCollection<YourObject> objs
and
public class YourObject : INotifyPropertyChanged
{
protected void OnPropertyChanged(string Name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(Name));
}
}
private string _p1;
public string p1
{
get { return _p1; }
set
{
if (_p1 != value)
{
_p1 = value;
OnPropertyChanged("p1");
}
}
}
private string _p2;
public string p2
{
get { return _p2; }
set
{
if (_p2 != value)
{
_p2 = value;
OnPropertyChanged("p2");
}
}
}
}

How to distinguish between same controls in MVVM?

I have 5 CheckBox and this is what they look like in the View:
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Content="Cb1"/>
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Content="Cb2"/>
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Content="Cb3"/>
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Content="Cb4"/>
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Content="Cb5"/>
This is the some of the code that I have in my ViewModel:
class CheckBoxesViewModel : INotifyPropertyChanged
{
public CheckBoxesViewModel()
{
CheckBoxes= new ObservableCollection<Models.CheckBoxes>();
_canExecute = true;
}
private bool _IsSelected;
public bool IsSelected
{
get
{
return _IsSelected;
}
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<Models.CheckBoxes> _checkBoxes = new ObservableCollection<Models.CheckBoxes>();
public ObservableCollection<Models.CheckBoxes> CheckBoxes
{
get { return _checkBoxes ; }
set
{
_checkBoxes = value;
OnPropertyChanged("CheckBoxes");
}
}
}
The problem is that when I check/uncheck one of the checkboxes it affects all of them.
I assume that is because they have exact same binding, but I can not figure out how to make the code distinguish them.
I think I could use Command and CommandParameters, but that does not seem like the best solution.
P.S. Do let me know if you see something wrong with my code - I am still trying to learn the whole MVVM thing.
You need to implement ICommand (google DelegateCommand to be able to treat an Action as ICommand) then you would bind the Command property of the CheckBox in the view to the Command on the view model.
ViewModel
public ICommand MyCommand { get; private set; }
.... MyCommand = new DelegateCommand((value) => this.DoStuff(value));
Xaml
<CheckBox Command={Binding MyCommand} Command Parameter={...} />
From your stated purpose in comments (which really should have been in your question--this is a classic XY problem)
You're attempting to route View logic through your ViewModel, which should be a hint that something's wrong here. Your stated purpose is
Each CheckBox has a corresponding TextBox that gets shown when it is checked. I was hoping to reuse the same code for all of the CheckBoxes and only change some value that helps me distinguish them (e.g. Content)
Toggling visibility is a View concern. You can do it thusly
<StackPanel>
<CheckBox x:Name = "cb1" />
<!-- cb2 through cbn omitted -->
<StackPanel />
<StackPanel>
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="btvc" />
</StackPanel.Resources>
<TextBox Text="{Binding FirstTextBox}"
Visibility="{Binding IsChecked,
ElementName=cb1,
Converter={StaticResource btvc}}" />
<!-- SecondTextBox through NthTextBox omitted -->
</StackPanel />
I'm toggling visibility of the textbox by whether or not the corresponding checkbox was checked.
Now, if you're trying to munge together N textbox values into one property... You're making life too hard on yourself.
If you wish to 'programmatically add them", then you need to reverse your logic. Instead of 'adding controls' to the form, you need to think of 'adding data' to your ViewModel. This is how you stay within MVVM guidelines, as your tag suggests.
Here's how you can reverse the logic....The ItemsControl has the ability to bind to a collection of ViewModels. This control also 'automagically' determines what control to use for each item in the collection by using DataTemplates.
Here's the XAML code:
<ItemsControl ItemsSource="{Binding myCollection}" >
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:myViewModelForItemA}">
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding aName}"></CheckBox>
</DataTemplate>
<DataTemplate DataType="{x:Type local:myViewModelForItemB}">
<RadioButton IsChecked="{Binding IsChecked}" Content="{Binding aName}"></RadioButton>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
As you can probably see, the Binding "myCollection" is the collection you add your ViewModel Instances to (this is an ObservableCollection).
Each DataTemplate in the IntemsControl.Resources is how you want each item to look (you can even place multiple controls inside each DataTemplate, just remember that whatever you bind these to will bind to the ViewModel in the collection (i.e. myViewModelForItemA, myViewModelForItemB).
The code in your main view model:
public ObservableCollection<object> myCollection { set; get; }
....
myCollection = new ObservableCollection<object>();
myViewModelForItemA anItem = new myViewModelForItemA();
myCollection.Add(anItem);
//now anItem of type (myViewModelForItemA) is in our collection
//and the ItemsControl automagically added a CheckBox to it's collection
//and bound isChecked to anItem.isChecked property, and bound
//the Content to anItem.aName property.

Silverlight - Can't get ListBox to bind to my ObservableCollection

I'm trying to make a ListBox that updates to the contents of an ObservableCollection whenever anything in that collection changes, so this is the code I've written for that:
xaml:
<ListBox x:Name="UserListTest" Height="300" Width="200" ItemsSource="listOfUsers">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C#:
public ObservableCollection<User> listOfUsers
{
get { return (ObservableCollection<User>)GetValue(listOfUsersProperty); }
set { SetValue(listOfUsersProperty, value); }
}
public static readonly DependencyProperty listOfUsersProperty =
DependencyProperty.Register("listOfUsers", typeof(ObservableCollection<User>), typeof(MainPage), null);
And I set up a call to a WCF Service that populates listOfUsers:
void repoService_FindAllUsersCompleted(object sender, FindAllUsersCompletedEventArgs e)
{
this.listOfUsers = new ObservableCollection<User>();
foreach (User u in e.Result)
{
listOfUsers.Add(u);
}
//Making sure it got populated
foreach (User u in listOfUsers)
{
MessageBox.Show(u.LastName);
}
}
The ListBox never populates with anything. I assume my problem may be with the xaml since the ObservableCollection actually has all of my Users in it.
You're missing the {Binding} part from your ItemsSource there.
<ListBox x:Name="UserListTest" Height="300" Width="200" ItemsSource="{Binding listOfUsers}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LastName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Also, you may not need to have a DependencyProperty for your list, you may be able to achieve what you need with a property on a class that implements INotifyPropertyChanged. This may be a better option unless you need a DependencyProperty (and the overhead that goes along with it) for some other reason.
e.g.
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<User> _listOfUsers;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<User> ListOfUsers
{
get { return _listOfUsers; }
set
{
if (_listOfUsers == value) return;
_listOfUsers = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("ListOfUsers"));
}
}
}

Wpf Combobox in Master/Detail MVVM

I have MVVM master /details like this:
<Window.Resources>
<DataTemplate DataType="{x:Type model:EveryDay}">
<views:EveryDayView/>
</DataTemplate>
<DataTemplate DataType="{x:Type model:EveryMonth}">
<views:EveryMonthView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Margin="12,24,0,35" Name="schedules"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Elements}"
SelectedItem="{Binding Path=CurrentElement}"
DisplayMemberPath="Name" HorizontalAlignment="Left" Width="120"/>
<ContentControl Margin="168,86,32,35" Name="contentControl1"
Content="{Binding Path=CurrentElement.Schedule}" />
<ComboBox Height="23" Margin="188,24,51,0" Name="comboBox1"
VerticalAlignment="Top"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Schedules}"
SelectedItem="{Binding Path=CurrentElement.Schedule}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValue="{Binding Path=CurrentElement.Schedule.ID}"/>
</Grid>
This Window has DataContext class:
public class MainViewModel : INotifyPropertyChanged {
public MainViewModel() {
elements.Add(new Element("first", new EveryDay("First EveryDay object")));
elements.Add(new Element("second", new EveryMonth("Every Month object")));
elements.Add(new Element("third", new EveryDay("Second EveryDay object")));
schedules.Add(new EveryDay());
schedules.Add(new EveryMonth());
}
private ObservableCollection<ScheduleBase> _schedules = new
ObservableCollection<ScheduleBase>();
public ObservableCollection<ScheduleBase> Schedules {
get {
return _schedules;
}
set {
schedules = value;
this.OnPropertyChanged("Schedules");
}
}
private Element _currentElement = null;
public Element CurrentElement {
get {
return this._currentElement;
}
set {
this._currentElement = value;
this.OnPropertyChanged("CurrentElement");
}
}
private ObservableCollection<Element> _elements = new
ObservableCollection<Element>();
public ObservableCollection<Element> Elements {
get {
return _elements;
}
set {
elements = value;
this.OnPropertyChanged("Elements");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
One of Views:
<UserControl x:Class="Views.EveryDayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid >
<GroupBox Header="Every Day Data" Name="groupBox1" VerticalAlignment="Top">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBox Name="textBox2" Text="{Binding Path=AnyDayData}" />
</Grid>
</GroupBox>
</Grid>
My SelectedItem in ComboBox doesn't works correctly. Are there any visible errors in my code?
What I usually do is bind the items of an ItemsControl to an ICollectionView (usually ListCollectionView) instead of directly to a collection; I think that's what the ItemsControl does by default anyway (creates a default ICollectionView), but I might be wrong.
Anyway, this allows you to work with the CurrentItem property of the ICollectionView, which is automatically synchronized with the selected item in an ItemsControl (if the IsSynchronizedWithCurrentItem property of the control is true or null/default). Then, when you need the current item in the ViewModel, you can use that instead. You can also set the selected item by using the MoveCurrentTo... methods on the ICollectionView.
But as I re-read the question I realize you may have another problem altogether; you have a collection of 'default' items and need a way to match them to specific instances. It would however be a bad idea to override the equality operators of the objects to consider them always equal if they are of the same type, since that has the potential to make other code very confusing. I would consider extracting the type information into an enum, and put a read-only property on each object returning one of the enum values. Then you can bind the items to a collection of the enum values, and the selected item to the enum property of each object.
Let me know if you need an example, I may have made a mess of the explanation :)

Categories

Resources