WPF ListBox: Binding to an ObservableCollection - wpf

I'm binding my ListBox to an ObservableCollection at runtime. Upon clicking a button, one of the items in my collection gets modified, but the corresponding ListBox item doesn't update itself accordingly. I have gone thru several similar SO articles and other help material and it seems like I'm doing everything they've asked for, but no luck. Everything seems to load and bind correctly, but when I change "IsLoading" property of an item in my collection, the Visibility of the Grid (see DataTemplate below) which is bound to IsLoading property doesn't change.
Following is my ListBox XAML:
<ListBox Name="lstItems">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Name="ListBoxGrid">
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" IsChecked="{Binding IsSelected}" />
<Image Grid.Column="1" Width="50" Stretch="Uniform" Source="{Binding Image}" />
<TextBlock Grid.Column="2" Text="{Binding Path=ImageFilePath}" />
<Grid Grid.Column="3" Visibility="{Binding IsLoading, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, Mode=TwoWay, BindsDirectlyToSource=True, Converter={StaticResource BooleanToVisibilityConverter1}}">
<my:LoadingAnimation x:Name="SendAnimation" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And here's my BO:
public class Order : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool IsSelected { get; set; }
public string ImageFilePath { get; set; }
public ImageSource Image { get; set; }
private bool mIsSending = false;
public bool IsSending
{
get { return mIsSending; }
set
{
mIsSending = value;
if (PropertyChanged != null)
PropertyChanged(null, new PropertyChangedEventArgs("IsSending"));
}
}
}
And this is how I create the collection and bind it:
ObservableCollection<Order> mOrders = new ObservableCollection<Order>();
public MainWindow()
{
InitializeComponent();
lstItems.ItemsSource = mOrders;
}

Nevermind. Sometimes it is that you spend hours digging the problem, finally get frustrated, post it on SO, and the next 2 minutes you figure it out yourself. For any future reader, the only problem was that I was sending null in PropertyChanged event. As soon as I changed that to this, things started working like a charm.

Related

DataTemplate for ViewModel slow to display

I have a simple MainWindowView
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ClassListForTeacherViewModel}">
<local:ClassListForTeacher />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="109*"/>
<ColumnDefinition Width="408*"/>
</Grid.ColumnDefinitions>
<local:NavigationControl Grid.Column="0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="Auto"/>
<ContentControl Content="{Binding MainView}"
Grid.Column="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="Auto"/>
</Grid>
<Window>
and a MainWindowViewModel
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel(string name)
: this(name, null)
{
}
public MainWindowViewModel(string name, ViewModelBase mainView)
{
Name = name;
MainView = mainView;
NavigateToTeacherClassesCommand = new DelegateCommand(o => NavigateToTeacherClasses());
}
//
// Properties
//
public string Name { get; private set; }
private ViewModelBase _MainView;
public ViewModelBase MainView
{
get
{
return _MainView;
}
private set
{
_MainView = value;
RaisePropertyChangedEvent("MainView");
}
}
public ICommand NavigateToTeacherClassesCommand { get; private set; }
//
// Functions
//
private void NavigateToTeacherClasses()
{
MainView = new ClassListForTeacherViewModel();
}
}
The ClassListForTeacher view only contains a ListView control with no logic (yet). When I click the Label in the NavigationControl in the MainWindowView, the ClassListForTeacher is set on the MainView property of MainWindowView and is displayed via the DataTemplate. This is barebones program; no loading data yet. My problem is the display takes too long, maybe about a full 3 seconds. It is my first time trying out MVVM, I see this is a common technique (using DataTemplate), is this normal?
I also noticed that the ViewModel for ClassListForTeacher is called twice but I have no other code calling it. One from the NavigateToTeacherClasses function, the other I don't know. When WPF actually resolves the DataTemplate (which is a UserControl), is the constructor called again? If so, How do I prevent this?
EDIT:
XAML for NavigationControl
<UserControl...
<StackPanel>
<Expander Header="Grading" IsExpanded="True">
<StackPanel Background="#FFE5E5E5">
<Label Content="My Classes">
<Label.InputBindings>
<MouseBinding Command="{Binding NavigateToTeacherClassesCommand}" MouseAction="LeftClick" />
</Label.InputBindings>
</Label>
</StackPanel>
</Expander>
</StackPanel>
</UserControl>
XAML for ClassListForTeacher:
<UserControl...
<Grid x:Name="LayoutRoot">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>

add buttons to table arrangement dynamically in WPF

I would like to make a 2-columns table arrangement and to add buttons to the table at runtime.
what I did is defining nested StackPanels similar to this.
<StackPanel MinWidth="500" MaxWidth="800" MaxHeight="400" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Foreground="Black" Margin="0,0,0,5" FontSize="20">Some Title</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="" MinWidth="100" MinHeight="100" Margin="10,0,0,10"></Button>
<Button Content="" MinWidth="100" MinHeight="100" Margin="10,0,0,10"></Button>
</StackPanel>
</StackPanel>
Is this a correct starting, or there is a better and easier arrangement?
You could use a model-view implementation with ListView and bind it to a collection of items which have the respective handler for the button:
WPF:
<ListView ItemsSource={Binding MyItems}>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding={Binding Name}></GridViewColumn>
<GridViewColumn Header="Button">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Command={Binding ButtonPress}>Click me</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You can also set the ItemsSource inside the .cs file for your view as well, otherwise use a ViewModel class to handle your view and create a ObservableCollection<MyItemWrapper> property which will hold all the table items.
ViewModel:
public class MyViewModel
{
private ObservableCollection<MyItemWrapper> _myItems;
public MyViewModel()
{
_myItems = new ObservableCollection<MyItemWrapper>();
//// add your initial items
}
public ObservableCollection<MyItemWrapper> MyItems
{
get { return _myItems; }
}
}
View:
public partial class MyView : UserControl
{
public MyView(MyViewModel viewModel)
{
DataContext = viewModel;
InitializeComponents()
}
}
MyItem and MyItemWrapper
public class MyItem
{
public string Name { get; set; }
public object Data { get; set; }
}
public class MyItemWrapper
{
private MyItem _item;
public MyItemWrapper(MyItem item)
{
_item = item;
ButtonPress = new DelegateCommand<object>(OnButtonPress);
}
public string Name
{
get { return _item.Name; }
}
public DelegateCommand<object> ButtonPress { get; private set; }
private void OnButtonPress(object args)
{
System.Diagnostics.Debug.WriteLine("Button pressed for: " + Name);
}
}
This will ultimately be able to add/remove items at runtime by using MyItems inside the view model and have your list view always update automatically.
I got it working by using WrapPanel, and I will share what I have did, Thanks for Vlad for binding part:
<ListBox ItemsSource="{Binding CartItemC.CartItems}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="wrapPanel" MaxWidth="300" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel MaxWidth="300">
<Button Command="{Binding ButtonPress}">Click me</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You mentioned you want it dynamically, well you don't want to use StackPanel as you would end up maintaining the width and height of the controls inside it. Given you want the controls to automatically resize its own size.
The best solution is to use Grid if you really want to make a 2 column table arrangement.
Take a look at this example.
Sample code
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>

MVVM Light Commands within an ItemsControl

I'm just trying my hand at WP7 dev using the MVVM Light framework.
I'm trying to fire a button command inside an ItemsControl, essentialy it's a list of cars and I'd like each element to have an edit button.
The Relevant piece of the View:
<ItemsControl ItemsSource="{Binding MyCars}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="CarViewGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="100" />
<ColumnDefinition Width="Auto" MinWidth="302"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="40" />
<RowDefinition Height="Auto" MinHeight="32" />
<RowDefinition Height="Auto" MinHeight="32" />
<RowDefinition Height="Auto" MinHeight="32" />
</Grid.RowDefinitions>
<TextBlock x:Name="CarName" Text="{Binding Name, Mode=TwoWay}" Margin="7,0" Grid.Row="0" Grid.ColumnSpan="2" FontSize="32" FontWeight="Bold" FontStyle="Normal" />
<TextBlock x:Name="Make" Text="{Binding Make, Mode=TwoWay}" Margin="15,0" Grid.Row="1" Grid.Column="0" FontSize="24" />
<TextBlock x:Name="Model" Text="{Binding Model, Mode=TwoWay}" Grid.Row="1" Grid.Column="1" FontSize="24" />
<TextBlock x:Name="Odometer" Text="{Binding Odometer, Mode=TwoWay}" Margin="15,0" Grid.Row="2" Grid.ColumnSpan="2" FontSize="24" />
<Button x:Name="EditCarButton" Content="Edit" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right" Width="100" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding EditCar}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
My ViewModel contains this:
public RelayCommand OpenNewForm { get; private set; }
public CarViewModel()
{
//Snip
EditCar = new RelayCommand<Car>(c =>
{
CurrentCar = c;
FormVisible = true;
});
}
Now as you can see I'm trying to pass the current Car object that is bound through the CommandParameter. My delegate never fires so I'm guessing I've got something wrong in my binding regarding the current DataContext.
Anybody got any ideas as to what I'm doing wrong?
In a DataTemplate, the DataContext is set by default to the item that is represented by the DataTemplate (in that case, the Car object). If the EditCar command is on the main viewmodel (which also contains the MyCars collection), you need to explicitly set the Source of the Binding to that object. This would be (assuming that you are using the MVVM Light's ViewModelLocator and that your VM is named Main) {Binding Source={StaticResource Locator}, Path=Main.EditCar}
Cheers,
Laurent
Its going to fire EditCar on a car item. There are a couple ways to solve this, since you're using mvvm light try.
Appologies to Laurent. I posted the wrong link. My intention was that since the original poster was using MVVM Light that Dan Wahlin's DataContextProxy or a RelativeSource binding solution would work. I was going to go on and explain how if using CM an event from a child item could bubble up but I didn't. The link to CM dotnetrocks was something I pasted previously.
I have found that its alot easier to make my collections VM collections instead of Entitycollections. I used to use entitycollections and then I started running into those problems like you are describing. But Now each VM in the Collection is 'selfaware' and can act on itself without jumping through major hoops.
You would have the button that you are clicking as part of the CarsVM and it would have access to all the properties of the carVM which would have access to all the properties of your Car Entity.
Sample from My App:
public partial class ReadmitPatientListViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the ReadmitPatientListViewModel class.
/// </summary>
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
#region Declarations
ICommand _openSurveyCommand;
Messenger _messenger = Messenger.Default;
#endregion
#region Command Properties
public ICommand OpenSurveyCommand
{
get
{
if (_openSurveyCommand == null)
{
_openSurveyCommand = new RelayCommand(() => OnSurveyCommandExecute());
}
return _openSurveyCommand;
}
private set { }
}
#endregion
#region Command Methods
private void OnSurveyCommandExecute()
{
Wait.Begin("Loading Patient List...");
_messenger.Send<ReadmitPatientListViewModel>(this);
_messenger.Send<Messages.NavigationRequest<SubClasses.URI.PageURI>>(GetNavRequest_QUESTIONAIRRESHELL());
}
#endregion
#region Properties
#endregion
private static Messages.NavigationRequest<SubClasses.URI.PageURI> GetNavRequest_QUESTIONAIRRESHELL()
{
Messages.NavigationRequest<SubClasses.URI.PageURI> navRequest =
new Messages.NavigationRequest<SubClasses.URI.PageURI>(
new SubClasses.URI.PageURI(Helpers.PageLinks.QUESTIONAIRRESHELL, System.UriKind.Relative));
return navRequest;
}
partial void OnCreated()
{
}
}
These are the properties in the primary vm that my Expander binds to:
public CollectionViewSource SearchResultsCVS { get; private set; }
public ICollection<ViewModel.ReadmitPatientListViewModel> SearchResults { get; private set; }
The collection is the soure for the CVS.....when the completeSurveyButton is clicked a navigation request is sent,and a copy of the viewmodel is sent to any listeners to manipulate.

How do I bind list items to an Accordian control from the Silverlight 3 Toolkit?

I have a list of objects in a model. I wish to show elements of the DTO's in the list in my AccordianItem panels. The model is like this:
public class MyModel
{
public List<AnimalDTO> Items { get; set; }
public MyModel()
{
Items = new List<AnimalDTO>
{
new AnimalDTO() {Title = "Monkey", ImageUri = "Images/monkey.jpg"},
new AnimalDTO() {Title = "Cow", ImageUri = "Images/cow.jpg"},
};
}
}
public class AnimalDTO
{
public string Title { get; set; }
public string LongDescription { get; set; }
public string ImageUri { get; set; }
public string NavigateUri { get; set; }
}
I want to show the image in the background image of AccordianItems and lay the LongDescription over a portion of the image.
If I hard code it, I can get the image in the AccordianItem thus...
<layoutToolkit:AccordionItem x:Name="Item2" Header="Item 2" Margin="0,0,10,0" AccordionButtonStyle="{StaticResource AccordionButtonStyle1}" ExpandableContentControlStyle="{StaticResource ExpandableContentControlStyle1}" HeaderTemplate="{StaticResource DataTemplate1}" BorderBrush="{x:Null}" ContentTemplate="{StaticResource CarouselContentTemplate}">
<layoutToolkit:AccordionItem.Background>
<ImageBrush ImageSource="Images/cow.jpg" Stretch="None"/>
</layoutToolkit:AccordionItem.Background>
</layoutToolkit:AccordionItem>
When I try it with a binding like <ImageBrush ImageSource="{Binding Path={StaticResource MyContentTemplate.ImageUri}}" Stretch="None"/> or if I try it with <ImageBrush ImageSource="{Binding Path=Items[0].ImageUri}" Stretch="None"/>
, it throws XamlParseException.
Edit:
I'm able to get some binding of the text over hard-coded images with the following StaticResource (NOTE: I'm hard-coding Items[2], I'm not sure how to index it)
<DataTemplate x:Key="CarouselContentTemplate">
<Grid Width="650" Height="420">
<Grid.RowDefinitions>
<RowDefinition Height="0.476*"/>
<RowDefinition Height="0.524*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
x:Name="Title"
Text="{Binding Items[2].Title}"
Foreground="Black" FontSize="12"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0"
x:Name="LongDescription"
Text="{Binding Items[2].LongDescription}"
TextWrapping="Wrap"FontSize="8"></TextBlock>
</Grid>
</DataTemplate>
Is there a way to index the Items collection in the DataTemplate? Furthermore how do I get the Image to bind rather than hard-coding them in each AccordianItem? Any help in the right direction would be appreciated, most especially how to bind and lay text over an image.
To bind to a collection it must be referenced with ItemsSource="{Binding Items}", where in this case Items is my collection MyModel.Items
<layoutToolkit:Accordion
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ExpandDirection="Right"
Style="{StaticResource AccordionStyle1}"
AccordionButtonStyle="{StaticResource AccordionButtonStyle1}"
MaxHeight="420" MaxWidth="800"
ItemsSource="{Binding Items}" Margin="8,0,-8,-12" Grid.Row="3"
>
<layoutToolkit:Accordion.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"></TextBlock>
</DataTemplate>
</layoutToolkit:Accordion.ItemTemplate>
Note that a collection should be bound with ItemsSource, which is plural as a mnemonic. and individual members of elements are bound within control of <layoutToolkit:Accordian.ItemTemplate> Here I am showing MyCollection.Title in a TextBlock control. I shall update this with full code or a link to my blog for a full example later.

databinding a Dataset to a listbox...Datatemplate needs to display from multiple columns

I was trying to figure this out for quite some time.I want a Databind a listbox with a Dataset.Simple as it can get.But my problem is that i want my datatemplate to display Columns from two tables,in the dataset.I have tried many samles..but everything i found just gives the dataset as datacontext and gives a single table as itemsource.But my condition is that i want more than one table in my datatemplate..
For eg:
<DataTemplate x:Key="EmployeeDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Margin="5" BorderBrush="Black" BorderThickness="1">
<Image Source="{Binding Path=Mast/Image}" Stretch="Fill" Width="50" Height="50" />
</Border>
<StackPanel Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal" TextBlock.FontWeight="Bold" >
<TextBlock Text="{Binding Path=Mast/Firstname}" />
<TextBlock Text="{Binding Path=Mast/Lastname}" Padding="3,0,0,0"/>
</StackPanel>
<TextBlock Text="{Binding Path=Details/Age}" />
<TextBlock Text="{Binding Path=Details/Role}" />
</StackPanel>
</Grid>
</DataTemplate>
Any way to do this..? I am confused...!
I tried giving the Dataset as datacontext and Itemsource as {Binding} But only one row is displayed...
You should create a view model class that exposes three properties:
MasterTable of type IEnumerable<MasterTableRow>
SelectedMaster of type DataRowView
MasterDetails of type IEnumerable<DetailsTableRow>
In your view model, put your instance of your DataSet, and return the appropriate values for the properties. To wrap it all up, you should implement INotifyPropertyChanged and fire change notifications for SelectedMaster and MasterDetails whenever SelectedMaster changes.
Remember to set the view model as the DataContext for the bindings.
Here's how it might look like:
public partial class ViewModel : INotifyPropertyChanged
{
DataSet1 ds;
DataRowView selectedMaster;
public IEnumerable<DataSet1.MasterTableRow> MasterTable
{
get { return ds.MasterTable; }
}
public DataRowView SelectedMaster
{
get { return selectedMaster; }
set
{
if (selectedMaster != value)
{
selectedMaster = value;
OnPropertyChanged("MasterDetails");
OnPropertyChanged("SelectedMaster");
}
}
}
public IEnumerable<DataSet1.DetailsTableRow> MasterDetails
{
get
{
var masterRow = (DataSet1.MasterTableRow)SelectedMaster.Row;
return masterRow.GetDetailsTableRows();
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
#endregion
}
In XAML, the bindings might look like this:
<ListBox ItemsSource="{Binding MasterTable}"
SelectedItem="{Binding SelectedMaster}"
ItemTemplate="{StaticResource MasterTemplate}"/>
<ListBox ItemsSource="{Binding MasterDetails}"
ItemTemplate="{StaticResource DetailsTemplate}"/>

Resources