I am using MVVM light in conjunction with EF4 and SQL CE 4, but I am having issues with my observable collection. My application doesn't neccessarily need to use the mvvm pattern, but since I need the benefits of an observablecollection I have decided to learn how to integrate it. I can successfully link my database of property entitites to my listbox and display them, I can also link some properties of these entities to textboxes, but where I am stuck is when I try to update these properties by typing in the textbox. Here is my xaml code for a textbox and the listbox:
<TextBox Text="{Binding SaleTitle, ValidatesOnDataErrors=true, Mode=TwoWay}"
<ListBox Height="424"
Margin="24,80,0,0"
x:Name="listBoxProperties"
VerticalAlignment="Top"
ItemTemplate="{StaticResource propertySummaryTemplate}"
IsSynchronizedWithCurrentItem="True"
Width="216" BorderThickness="0" Background="{x:Null}"
FontFamily="Segoe UI"
ItemsSource="{Binding PropertyList}"
SelectedItem="{Binding CurrentProperty, Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
UseLayoutRounding="True"
HorizontalAlignment="Left"
ScrollViewer.VerticalScrollBarVisibility="Disabled" >
</ListBox>
Here is the code of part of my MainViewModel:
private string _SaleTitle;
public string SaleTitle
{
get
{
if (CurrentProperty != null)
return CurrentProperty.SaleTitle;
else
return "";
}
set
{
_SaleTitle = value;
RaisePropertyChanged("SaleTitle");
}
}
private RelayCommand loadCommand;
public ICommand LoadCommand
{
get
{
if (loadCommand == null)
loadCommand = new RelayCommand(() => Load());
return loadCommand;
}
}
private void Load()
{
PropertyList = new ObservableCollection<Property>((from property in entities.Properties.Include("Images")
select property));
propertyView = CollectionViewSource.GetDefaultView(PropertyList);
if (propertyView != null)
propertyView.CurrentChanged += new System.EventHandler(propertyView_CurrentChanged);
RaisePropertyChanged("CurrentContact");
RaisePropertyChanged("SaleTitle");
RaisePropertyChanged("Address");
RaisePropertyChanged("AuctioneerName");
RaisePropertyChanged("AgentName");
RaisePropertyChanged("Price");
RaisePropertyChanged("NextBid");
RaisePropertyChanged("Status");
}
void propertyView_CurrentChanged(object sender, System.EventArgs e)
{
RaisePropertyChanged("CurrentContact");
RaisePropertyChanged("SaleTitle");
RaisePropertyChanged("Address");
RaisePropertyChanged("AuctioneerName");
RaisePropertyChanged("AgentName");
RaisePropertyChanged("Price");
RaisePropertyChanged("NextBid");
RaisePropertyChanged("Status");
}
private Property _CurrentProperty;
public Property CurrentProperty
{
get
{
if (propertyView != null)
return propertyView.CurrentItem as Property;
return null;
}
set
{
_CurrentProperty = value;
RaisePropertyChanged("CurrentProperty");
}
}
public ObservableCollection<Property> PropertyList
{
get
{
return propertyList;
}
set
{
if (propertyList == value)
{
return;
}
var oldValue = propertyList;
propertyList = value;
// Update bindings, no broadcast
RaisePropertyChanged(PropertiesPropertyName);
}
}
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
}
else
{
// Code runs "for real"
entities = new Model1Container1();
}
}
////public override void Cleanup()
////{
//// // Clean up if needed
//// base.Cleanup();
////}
}
}
The listbox is populated successfully with the content from current selected item, but when I type in it and click out of it or do anything to lose focus it simply goes back to what was there before.
Take a look at your SaleTitle property definition. It Reads value from CurrentProperty.Saletitle but sets value to local field which is not used anythere.
Related
I'm working on a WPF program for editing a proprietary script class. The model is composed of the following classes:
public MethodCall
{
public Dictionary<string, object> Parameters { get; }
public MethodCall()
{
Parameters = new Dictionary<string, object>();
}
public MethodCall(Dictionary<string, object> parameters)
{
Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters);
}
public void Execute(Client client)
{
// Does stuff
}
}
public class Script
{
public List<Request> Requests { get; }
public void Execute(Client client)
{
foreach (var request in Requests)
{
request.Execute(client);
}
}
}
To display a Script containing a CallMethod request, I have the following View Model classes.
public class ScriptViewModel : INotifyPropertyChanged
{
public Script Script { get; set; } // Setter raises PropertyChanged
public ObservableCollection<RequestViewModel> { get; }
}
public class RequestViewModel : INotifyPropertyChanged
{
public Request Request { get; set; } // Setter raises PropertyChanged
public ObservableCollection<ParameterViewModel> Parameters { get; }
}
public class ParameterViewModel : INotifyPropertyChanged
{
public bool IsDirty { get; set; } // Raises PropertyChanged
public string Name { get; set; } // Raises PropertyChanged
public object Value { get; set; } // Raises PropertyChanged
}
Currently, I'm using a DataGrid control to display & edit the Parameters collection. But, because the Value property is an object, it can be anything, including an array of objects. My problem is how to edit this data in the WPF app. I'm using a DataGridTextColumn to display the Name property, and that works fine. Currently, I'm also using a DataGridTextColumn to display the Value property, but that's not working so well.
I think I want to use a DataGridTemplateColumn' for theValueproperty, but I'd need to use different templates depending upon what the particular type is. If the type isn't an array, I'd use a TextBox, but if it is an Array, I'd probably use anotherDataGrid`.
How do I write that XAML?
Considering how little traction this question got, I ended up rolling my own solution. I share it here in case someone else can make use of it in a similar situation.
What I did first was to define a number of different DataTemplates in the App.xaml:
<DataTemplate x:Key="ObjectArrayTemplate"
DataType="system:Array">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<DataGrid x:Name="ParameterItemsGrid"
Grid.Column="0"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeRows="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
Initialized="OnArrayDataGridInitialized">
<DataGrid.Columns>
<DataGridTemplateColumn CellTemplateSelector="{StaticResource ValueDataSelector}"
Width="*" />
<DataGridTemplateColumn Width="52">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Button Command="local:EditorCommands.DeleteItem"
CommandParameter="{Binding Path=Item, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}}}"
Content="Delete"
Height="25"
Margin="5"
VerticalAlignment="Top" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1">
<Button Content="Add Item"
Command="local:EditorCommands.AddItem"
Margin="5" />
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ValueArrayTemplate"
DataType="{x:Type system:Array}">
<DataGrid Initialized="OnArrayDataGridInitialized">
<DataGrid.Columns>
<DataGridTextColumn Header="Item" />
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
<DataTemplate x:Key="ValueTemplate"
DataType="system:Object">
<TextBox Initialized="OnValueTextBoxInitialized" />
</DataTemplate>
The first DataTemplate is used to display a parameter whose type is an array of objects defined in the program. The second is used to display an array of objects of a simple type (int, string, etc.). The third template is used to display a parameter whose type is just a simple type, or an item whose value is a simple type.
There are other templates for different program-specific classes that I'm not going to show. The important points regarding these are that I implemented INotifyPropertyChanged on all of these classes and I had to set the UpdateSourceTrigger on the binding to PropertyChanged. I did all of this so the bindings would be 2 way and work.
The trick to making this work can be found in the Initialized event handler defined in the 2 DataGrids and the single simple type TextBox:
private void OnArrayDataGridInitialized(object sender, EventArgs e)
{
var valueGrid = (DataGrid)sender;
var parameterVm = (ParameterViewModel)valueGrid.DataContext;
// Bind the value grid's ItemsSource property to the ParameterViewModel's Items property.
var itemsBinding = new Binding("Items")
{
Source = parameterVm,
ValidatesOnDataErrors = true
};
valueGrid.SetBinding(DataGrid.ItemsSourceProperty, itemsBinding);
// Bind the value grid's SelectedItem property to the ParameterViewModel's CurrentItem property.
var currentItemBinding = new Binding("CurrentItem")
{
Mode = BindingMode.TwoWay,
Source = parameterVm,
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus
};
valueGrid.SetBinding(DataGrid.SelectedItemProperty, currentItemBinding);
// Bind the value grid's SelectedIndex property to the ParameterViewModel's CurrentItemIndex property.
var currentItemIndexBinding = new Binding("CurrentItemIndex")
{
Mode = BindingMode.TwoWay,
Source = parameterVm,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
valueGrid.SetBinding(DataGrid.SelectedIndexProperty, currentItemIndexBinding);
}
private void OnValueTextBoxInitialized(object sender, EventArgs e)
{
TextBox textBox = (TextBox)sender;
DataGridRow parentRow = VisualTreeHelpers.FindAncestor<DataGridRow>(textBox);
if (!(parentRow.Item is ParameterViewModel parameterVm))
return;
var valueBinding = new Binding("Value")
{
Mode = BindingMode.TwoWay,
Source = parameterVm,
ValidatesOnDataErrors = true
};
textBox.SetBinding(TextBox.TextProperty, valueBinding);
}
}
This code lives in the App.xaml.cs file and uses the VisualTreeHelpers class, which you can find in this post on Rachel Lim's blog.
As you can see, the OnArrayDataGridInitialized event handler finds the parent DataGrid control, then retrieves its DataContext and casts it to a ParameterViewModel. From that, it creates the needed bindings to connect the view to the objects.
In the PrameterViewModel class, I had to add an ObservableCollection<object> property called Items which would hold the individual array elements if the property is an array. I also added an object property called CurrentItem and an int property called CurrentItemIndex. These are referenced in the above XAML & Initialized event handlers.
The setter for the Value property now looks like this:
public object Value
{
get => _value;
set
{
if (_value == value)
return;
if (_value is INotifyPropertyChanged npc)
npc.PropertyChanged -= OnItemPropertyChanged;
_value = value;
RaisePropertyChanged();
npc = _value as INotifyPropertyChanged;
if (npc != null)
npc.PropertyChanged += OnItemPropertyChanged;
IsDirty = true;
RaisePropertyChanged(nameof(IsValid));
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
RaisePropertyChanged(nameof(Value));
}
In the constructor, after initializing the Items collection, I subscribe to it's CollectionChanged event; here's how I process those events:
private void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (object item in e.OldItems)
{
if (item is INotifyPropertyChanged npc)
npc.PropertyChanged -= OnItemPropertyChanged;
}
}
if (e.NewItems != null)
{
foreach (object item in e.NewItems)
{
if (item is INotifyPropertyChanged npc)
npc.PropertyChanged += OnItemPropertyChanged;
}
}
Finally, I had to make some changes to the ParameterViewModel class to get everything to display & update properly. Here is the ParameterViewModel constructor:
public ParameterViewModel(
string objectName,
string method,
string name,
Type type,
object value,
bool isNameReadonly = false) : this(findContext)
{
if (string.IsNullOrEmpty(objectName))
throw new ArgumentNullException(nameof(objectName));
if (string.IsNullOrEmpty(method))
throw new ArgumentNullException(nameof(method));
_name = name ?? throw new ArgumentException(nameof(name));
_type = type ?? throw new ArgumentNullException(nameof(type));
_elementType = _type.GetElementType() ?? _type;
_value = value;
if (_value != null && _type.IsArray && ((Array)_value).Length > 0)
LoadItems(_value, _elementType);
RaisePropertyChanged(nameof(IsValid));
}
Here's the LoadItems method that the constructor calls:
private void LoadItems(object value, Type type)
{
if (type.IsClass)
{
foreach (object item in (object[])_value)
{
Items.Add(item);
if (item is INotifyPropertyChanged npc)
npc.PropertyChanged += OnItemPropertyChanged;
}
return;
}
if (type == typeof(bool))
{
LoadItems((bool[])value);
}
else if (type == typeof(byte))
{
LoadItems((byte[])value);
}
else if (type == typeof(char))
{
LoadItems((char[])value);
}
else if (type == typeof(DateTime))
{
LoadItems((DateTime[])value);
}
else if (type == typeof(decimal))
{
LoadItems((decimal[])value);
}
else if (type == typeof(double))
{
LoadItems((double[])value);
}
else if (type == typeof(float))
{
LoadItems((float[])value);
}
else if (type == typeof(int))
{
LoadItems((int[])value);
}
else if (type == typeof(long))
{
LoadItems((long[])value);
}
else if (type == typeof(sbyte))
{
LoadItems((sbyte[])value);
}
else if (type == typeof(short))
{
LoadItems((short[])value);
}
else if (type == typeof(string))
{
LoadItems((string[])value);
}
else if (type == typeof(TimeSpan))
{
LoadItems((TimeSpan[])value);
}
else if (type == typeof(uint))
{
LoadItems((uint[])value);
}
}
private void LoadItems<T>(T[] array)
{
foreach (T item in array)
{
Items.Add(item);
}
}
That's pretty much it. There was no way to do the whole thing in XAML because the DataGrid control doesn't participate in the Visual Tree structure like a normal control. When the program is running, you can navigate to everything normally using Snoop, but the bindings don't inherit normally. This works and, with the custom templates for custom types, everything is easy for the user to read and navigate.
I have an ObservableCollection bound to a ListBox. Selecting an item in the list box populates a user control with it's own viewmodel based on the selected item. I am using a Linq to SQL DataContext for getting data from my model to the viewmodels.
The problem is that the displaymember for the listbox is bound to a property that combines two fields, a number and a date, for the item. The usercontrol allows the user to change the date, and I want that to be reflected in the list box immediately.
I initialize the collection and add in CollectionChanged and PropertyChanged handlers so that the collection is listening for the changes to properties within the collection:
public void FillReports()
{
if (oRpt != null) oRpt.Clear();
_oRpt = new ViewableCollection<Reportinformation>();
//oRpt.CollectionChanged += CollectionChanged; //<--Don't need this
foreach (Reportinformation rpt in _dataDc.Reportinformations.Where(x => x.ProjectID == CurrentPrj.ID).OrderByDescending(x => x.Reportnumber))
{
oRpt.Add(rpt);
}
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e != null)
{
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged rpt in e.OldItems)
{
rpt.PropertyChanged -= item_PropertyChanged;
}
}
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged rpt in e.NewItems)
{
rpt.PropertyChanged += item_PropertyChanged;
}
}
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
string s = sender.GetType().ToString();
if(s.Contains("Reportinformation"))
RaisePropertyChangedEvent("oRpt"); //This line does get called when I change the date
else if (s.Contains("Observation"))
{
RaisePropertyChangedEvent("oObs");
RaisePropertyChangedEvent("oObsByDiv");
}
}
The date gets changed correctly and the change persists and is written back to the database, but the change does not reflect in the listbox unless I actually change the collection (which happens when I switch jobs on another control in the same window as the listbox). The line in my property changed handler raises the change event for "oRpt" which is the observable collection bound to the ListBox, and changing the date does call the handler as verified with the debugger:
<ListBox x:Name="lsbReports" ItemsSource="{Binding oRpt}" DisplayMemberPath="ReportLabel" SelectedItem="{Binding CurrentRpt}"
Grid.Row="1" Grid.Column="0" Height="170" VerticalAlignment="Bottom" BorderBrush="{x:Null}" Margin="0,0,5,0"/>
But it seems that simply raising that change doesn't actually trigger the view to refresh the "names" of the items in the listbox. I have also tried to Raise for the ReportLabel bound to the DisplayMemberPath, but that doesn't work (worth a try though). I'm not sure where to go from here, as I think it's bad practice to reload the oRpt collection based on changing the date (therefore the name) of one of the actual items as I expect this database to grow fairly quickly.
Here is the Reportinformation extension class (this is an auto generated LinqToSQL class, so just my part is below):
public partial class Reportinformation // : ViewModelBase <-- take this out INPC already hooked up
{
public ViewableCollection<Person> lNamesPresent { get; set; }
public string ShortDate
{
get
{
DateTime d = (DateTime)Reportdate;
return d.ToShortDateString();
}
set
{
DateTime d = DateTime.Parse(value);
if (d != Reportdate)
{
Reportdate = DateTime.Parse(d.ToShortDateString());
SendPropertyChanged("ShortDate");//This works and uses the LinqToSQL call not my ViewModelBase call
SendPropertyChanged("ReportLabel"); //use the LinqToSQL call
//RaisePropertyChangedEvent("ReportLabel"); //<--This doesn't work
}
}
}
public string ReportLabel
{
get
{
return string.Format("{0} - {1}", Reportnumber, ShortDate);
}
}
public void Refresh()
{
RaisePropertyChangedEvent("oRpt");
}
public string RolledNamesString
{
get
{
if (lNamesPresent == null) return null;
return string.Join("|",lNamesPresent.Where(x=>x.Name!= "Present on Site Walk").Select(x=>x.Name).ToArray());
}
}
}
ANSWER
So my mistake was that I was adding to the LinqToSQL partial classes, and was using my ViewModelBase there which reimplements all of the INPC stuff over top of the autogenerated partial class. I undid that, and just use the INPC from the autogenerated designer stuff and it all works as expected. Thanks to SledgeHammer for chatting and making me rethink all of this!
You can solve this one of two ways. Either your ReportInformation class needs to implement INotifyPropertyChanged and raise the property changed events for the ReportLabel property whenever it changes:
public class ReportInformation : INotifyPropertyChanged
{
private int _numberField;
private DateTime _dateField;
public int NumberField
{
get => _numberField;
set
{
if (_numberField != value)
{
_numberField = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(ReportLabel));
}
}
}
public DateTime DateField
{
get => _dateField;
set
{
if (_dateField != value)
{
_dateField = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(ReportLabel));
}
}
}
public string ReportLabel => $"{NumberField}: {DateField}";
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName]string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
OR, you can use in your ListBox an ItemTemplate rather than DisplayMemberPath like so:
<ListBox x:Name="lsbReports"
ItemsSource="{Binding oRpt}"
SelectedItem="{Binding CurrentRpt}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NumberField}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding DateField}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have two different objects that are pointing at each other. The first object represents a division in a company. That object has two collection: Employees, which is all the employees working in the division and Project, which is all the special projects that are in progress within that division. So the first object looks like this:
public class Division : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
ObservableCollection<Employee> _employees;
ObservableCollection<Project> _projects;
public Division()
{
Employees = new ObservableCollection<Employee>();
Projects = new ObservableCollection<Project>();
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set
{
if (_employees != value)
{
_employees = value;
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}
}
}
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
if (_projects != value)
{
_projects = value;
PropertyChanged(this, new PropertyChangedEventArgs("Projects"));
}
}
}
public void AddNewProject()
{
this.Projects.Add(new Project(this));
}
}
Notice that when adding a new project to the division, I pass a reference to the division into that project, which looks like this:
public class Project : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
string _projectName;
DateTime _deadline = DateTime.Now;
Division _division;
ObservableCollection<Employee> _members;
public Project()
{
Members = new ObservableCollection<Employee>();
}
public Project(Division div)
{
Members = new ObservableCollection<Employee>();
Division = div;
}
public string ProjectName
{
get { return _projectName; }
set
{
if (_projectName != value)
{
_projectName = value;
PropertyChanged(this, new PropertyChangedEventArgs("ProjectName"));
}
}
}
public DateTime Deadline
{
get { return _deadline; }
set
{
if (_deadline != value)
{
_deadline = value;
PropertyChanged(this, new PropertyChangedEventArgs("Deadline"));
}
}
}
public Division Division
{
get { return _division; }
set
{
if (_division != value)
{
if (_division != null)
{
_division.Employees.CollectionChanged -= members_CollectionChanged;
}
_division = value;
if (_division != null)
{
_division.Employees.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Division"));
}
}
}
public ObservableCollection<Employee> Members
{
get { return _members; }
set
{
if (_members != value)
{
if (_members != null)
{
_members.CollectionChanged -= members_CollectionChanged;
}
_members = value;
if (_members != null)
{
_members.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Members"));
}
}
}
public ObservableCollection<Employee> AvailableEmployees
{
get
{
if (Division != null){
IEnumerable<Employee> availables =
from s in Division.Employees
where !Members.Contains(s)
select s;
return new ObservableCollection<Employee>(availables);
}
return new ObservableCollection<Employee>();
}
}
void members_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableEmployees"));
}
}
The reason I'm doing it like this is, that the project could have any type of team working on it, but only from within the division. So, when building a dashboard for the division, the manager could select any of the employees to that project but without putting in an employee that is already assigned to it. So, the AvailableEmployees property in the project object always keeps track of who is not already assigned to that project.
The problem I'm having is how to translate this into a UI. The experiment I've done so far looks like this:
<UserControl x:Class="Test.Views.TestView"
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"
mc:Ignorable="d"
xmlns:local="clr-namespace:Test.Views"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<ListBox ItemsSource="{Binding Div.Projects}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="Transparent"
BorderThickness="0, 0, 0, 2"
BorderBrush="Black"
Margin="0, 0, 0, 5"
Padding="0, 0, 0, 5">
<StackPanel>
<TextBox Text="{Binding ProjectName}"/>
<ListBox ItemsSource="{Binding Members}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
Text="{Binding FirstName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add Employee to Project"
Command="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AddEmployeeToProject}"
CommandParameter="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add New Project"
Command="{Binding AddNewProject}" />
</StackPanel>
The view model associated with this view is as follows:
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private Division _div;
public TestViewModel(Division div)
{
Div = div;
AddNewProject = new DelegateCommand(OnAddNewProject);
AddEmployeeToProject = new DelegateCommand<Project>(OnAddEmployeeToProject);
}
public DelegateCommand AddNewProject { get; set; }
public DelegateCommand<Project> AddEmployeeToProject { get; set; }
public Division Div
{
get { return _div; }
set
{
if (_div != value)
{
_div = value;
PropertyChanged(this, new PropertyChangedEventArgs("Div"));
}
}
}
private void OnAddNewProject()
{
Div.AddNewProject();
}
private void OnAddEmployeeToProject(Project proj)
{
var availables = proj.AvailableEmployees;
if (availables.Count > 0)
{
proj.Members.Add(availables[0]);
}
}
}
However, I cannot get the combobox for each employee in each project to work. It seems like the selected item/value is bound to the itemssource, and each time the combobox turns out blank. I've tried to do this also with SelectedValue and SelectedItem properties for the combobox, but none worked.
How do I get these two separated. Is there anything else I'm missing here?
OK. After so many experiments the best solution I came up with was to create my own user control that is composed of both a button and a combobox that imitate the behavior I was expecting of the combobox on it own.
First, I had a really stupid mistake in the model where both lists of members Project and Division contain the same instances of Employee, which makes the AvailableEmployees property buggy. What I really needed to do is to create a list of copies of employees in the Project instead of just references.
In any case, I created a new user control and called it DynamicSourceComboBox. The XAML of this control looks like this:
<Grid>
<Button x:Name="selected"
Content="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=SelectedValue}"
Click="selected_Click"/>
<ComboBox x:Name="selections"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=ItemsSource}"
DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=DisplayMemberPath}"
Visibility="Collapsed"
SelectionChanged="selections_SelectionChanged"
MouseLeave="selections_MouseLeave"/>
</Grid>
I have here a few bindings from the button and the combobox to properties in my user control. These are actually dependency properties. The code-behind of my user control looks like this:
public partial class DynamicSourceComboBox : UserControl
{
public DynamicSourceComboBox()
{
InitializeComponent();
}
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(DynamicSourceComboBox), new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
ComboBox.ItemsSourceProperty.AddOwner(typeof(DynamicSourceComboBox));
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty =
ComboBox.DisplayMemberPathProperty.AddOwner(typeof(DynamicSourceComboBox));
private void selected_Click(object sender, RoutedEventArgs e)
{
selected.Visibility = Visibility.Hidden;
selections.Visibility = Visibility.Visible;
selections.IsDropDownOpen = true;
}
private void selections_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
if (e.AddedItems.Count == 1)
{
var item = e.AddedItems[0];
Type itemType = item.GetType();
var itemTypeProps = itemType.GetProperties();
var realValue = (from prop in itemTypeProps
where prop.Name == DisplayMemberPath
select prop.GetValue(selections.SelectedValue)).First();
SelectedValue = realValue;
}
}
private void selections_MouseLeave(object sender, MouseEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
}
}
These dependency properties imitate the properties with similar names in ComboBox but they are hooked up to the internal combobox and the button in a way that makes them behave together as a single complex combobox.
The Click event in the button hides it and present the combobox to make the effect of just a box that is opening. Then I have a SelectionChanged event in the combobox firing to update all the needed information and a MouseLeave event just in case the user doesn't make any real selection change.
When I need to use the new user control, I set it up like this:
<local:DynamicSourceComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorLevel=1, AncestorType=ListBox}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
SelectedValue="{Binding FirstName, Mode=TwoWay}"/>
Of course, for all of it to work, I have to make a lot of hookups with PropertyChanged events in the models, so the Projects instance will know to raise a PropertyChanged event for AvailableEmployees any time a change is made, but this is not really the concern of this user control itself.
This is a pretty clunky solution, with a lot of extra code that is a bit hard to follow, but it's really the best (actually only) solution I could have come up with to the problem I had.
I have a particular scenarios. My application looks like this.
In the left side there are some User list Which is a ListBox and at the right side few fields which are data binding to left side. How it works is, if you select "User 1" in the right side user 1 related information will appear and you can modify the information and its is data binding with "UpdateSourceTrigger=PropertyChanged" so it immediately reflects at the left side too. Same case for other users.
Now the problem is if I select multiple users and edit a field say Field 3 which is Editable a textBox. Now If I select user 1 and edit this textbox it reflects in the user 1 "Note: ... " and if I select user 2 and edit the Field 3 it updates the User 2 "Note: ... " but in case of multi selection How do I achieve it? Suppose I want to select user 1 and User 2 both and Edit the Note field It should update both the note fields of user 1 and user 2 and Data binding should also work I mean it should immediately the text i am entering into the textbox. Any ideas how can I achieve this?
Currently in my viewModel
Model
public String Note
{
get
{
return (String)GetValue(NoteProperty);
}
set { SetValue(NoteProperty, value); }
}
View
and in XAML the User ListBox Items template is defined like this
<TextBlock Text="{Binding Note, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
and in the XAML the rightside textbox (field 3) is data bound in the same manner
<TextBox Text="{Binding Note, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
How do I achieve multiple users data binding?
Please help and give me some ideas.
EDIT:
Converter:
public class MultiBindingConverter : IValueConverter
{
ObservableCollection<Info> mycollection;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var coll = (ObservableCollection<Info>)value;
mycollection = coll;
if (coll.Count == 1)
{
if (parameter.ToString() == "FNote")
return coll[0];
}
else if (coll.Count > 1)
{
// string name = coll[0].FirstName;
if (parameter.ToString() == "FNote")
{
string name = coll[0].Note;
foreach (var c in coll)
{
if (c.Note != name)
return null;
else continue;
}
return name;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter.ToString() == "FNote")
{
foreach (var c in mycollection)
{
c.Note = value.ToString();
}
return mycollection;
}
return null;
}
}
For me only one TextBox Editable NoteTextBox needs to to be DataBinded with multiple Users.
In my ViewModel
I have written
ViewModel
private Command selectionChangedCommand;
public Command SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
{
selectionChangedCommand = new Command(SelectionChanged, true);
}
return selectionChangedCommand;
}
set { selectionChangedCommand = value; }
}
public void SelectionChanged(object value)
{
selectedItem = new ObservableCollection<Info>((value as IEnumerable).OfType<Info>());
}
private ObservableCollection<Info> selectedItem;
public ObservableCollection<Info> SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
PropertyChanged("SelectedItem");
}
}
In the Info class there is one property Note which needs to be binded to the View's two places.
I fully agree with #GazTheDestroyer ... this kind of Data Binding can not be achieved through Data binding alone. What #Kumar has suggested is working as a POC, but when you are in a live project and you play with model, viewModel and view and many UserControl with one view model or one User control with two ViewModels, then the difficulty of achieving this scenario is beyond guessing.
Ok, no more theory. I have achieved this and I am going to share how I did so.
One-to-one DataBinding is perfect and working fine. When you select User 4 This user Note field and Field3 Editable NoteBox are bound to the same Property, so it works perfectly.
In multiple selection say User4 is selected first, then you select User3 and user1, I put a logic in code behind that when multiple items are selected Note text is empty. This is not against
MVVM as updating a view based on some criteria of view is not breaking MVVM pattern. So now when the editable text box is updated with some text user4 properties is updated in viewModel. Now the difficult part is to update the other selected users. Here is the code that will update the selected users and will reflect as I have mentioned Mode="TwoWay", UpdateSourceTriger="PropertyChanged"
if (listUser.SelectedItems.Count > 1)
{
for (int i = 0; i < listUser.SelectedItems.Count; i++)
{
Info info = listUser.SelectedItems[i] as Info;
info.Note = (string)tbNote.Text;
}
}
In this way the value of the Editable note textbox is updated in the properties of all the users Note Property and as the binding is two-way, it will reflect in other users too.
There might be many way to solve it, but I found this way and it's working superbly, so I thought I'd answer my own question.
You cannot achieve this via databinding alone, since there are situations where you need to make logical decisions.
For instance, if user1 and user2 have different notetext, then when both are selected you cannot show both at the same time. Instead I guess you want some method of specifying that you want to "keep original text", or allow user to over type to set both texts to be the same.
Whatever you intend, you need to have separate binding sources in your viewmodel so that you can update them independently and make logical decisions.
I tried something with i know and i got output just as your requirement.Please correct me if i'm wrong.
XAML
<Window x:Class="MVVM_sample_ListBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVM_sample_ListBox"
Title="MainWindow" Height="350" Width="525"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<Window.Resources>
<local:Converter x:Key="Converter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="235*" />
<ColumnDefinition Width="268*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="lb" SelectionMode="Multiple" Grid.Row="0" ItemsSource="{Binding MyCollection}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseUp" >
<i:InvokeCommandAction CommandParameter="{Binding SelectedItems, ElementName=lb}" Command="{Binding SelectionChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding SecondName}"/>
<TextBlock Text="{Binding Company}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" >
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=FName, Converter={StaticResource Converter}}" Name="textBox1" VerticalAlignment="Top" Width="120" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=SName, Converter={StaticResource Converter}}" Name="textBox2" VerticalAlignment="Top" Width="120" />
<TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Text="{Binding SelectedItem,ConverterParameter=Comp, Converter={StaticResource Converter}}" Name="textBox3" VerticalAlignment="Top" Width="120" />
</StackPanel>
</Grid>
</Window>
C#
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
Model
public class Model : INotifyPropertyChanged
{
private string fname;
public string FirstName
{
get { return fname; }
set { fname = value;RaisePropertyChanged("FirstName"); }
}
private string sname;
public string SecondName
{
get { return sname; }
set { sname = value; RaisePropertyChanged("SecondName");}
}
private string company;
public string Company
{
get { return company; }
set { company = value;RaisePropertyChanged("Company"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if(PropertyChanged!= null)
{
this.PropertyChanged(this,new PropertyChangedEventArgs(name));
}
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
private MyCommand selectionChangedCommand;
public MyCommand SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
{
selectionChangedCommand = new MyCommand(SelectionChanged);
}
return selectionChangedCommand;
}
set { selectionChangedCommand = value; }
}
public void SelectionChanged(object value)
{
SelectedItem = new ObservableCollection<Model>((value as IEnumerable).OfType<Model>());
}
private ObservableCollection<Model> selectedItem;
public ObservableCollection<Model> SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; RaisePropertyChanged("SelectedItem"); }
}
private ObservableCollection<Model> mycoll;
public ObservableCollection<Model> MyCollection
{
get { return mycoll;}
set { mycoll = value;}
}
public ViewModel()
{
SelectedItem = new ObservableCollection<Model>();
SelectedItem.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(SelectedItem_CollectionChanged);
MyCollection = new ObservableCollection<Model>();
MyCollection.Add(new Model { FirstName = "aaaaa", SecondName = "bbbbb", Company = "ccccccc" });
MyCollection.Add(new Model { FirstName = "ddddd", SecondName = "bbbbb", Company = "eeeeeee" });
MyCollection.Add(new Model { FirstName = "fffff", SecondName = "gggggg", Company = "ccccccc" });
}
void SelectedItem_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//this.SelectedItem =new ObservableCollection<Model>((sender as ObservableCollection<Model>).Distinct());
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if(PropertyChanged!= null)
{
this.PropertyChanged(this,new PropertyChangedEventArgs(name));
}
}
}
public class MyCommand : ICommand
{
private Action<object> _execute;
private Predicate<object> _canexecute;
public MyCommand(Action<object> execute, Predicate<object> canexecute)
{
_execute = execute;
_canexecute = canexecute;
}
public MyCommand(Action<object> execute)
: this(execute, null)
{
_execute = execute;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (parameter == null)
return true;
if (_canexecute != null)
{
return _canexecute(parameter);
}
else
{
return true;
}
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion
}
Converter
public class Converter : IValueConverter
{
ObservableCollection<Model> mycollection;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var coll = (ObservableCollection<Model>)value;
mycollection = coll;
if (coll.Count == 1)
{
if (parameter.ToString() == "FName")
return coll[0].FirstName;
else if (parameter.ToString() == "SName")
return coll[0].SecondName;
else if (parameter.ToString() == "Comp")
return coll[0].Company;
}
else if(coll.Count >1)
{
// string name = coll[0].FirstName;
if (parameter.ToString() == "FName")
{
string name = coll[0].FirstName;
foreach (var c in coll)
{
if (c.FirstName != name)
return null;
else continue;
}
return name;
}
if (parameter.ToString() == "SName")
{
string name = coll[0].SecondName;
foreach (var c in coll)
{
if (c.SecondName != name)
return null;
else continue;
}
return name;
}
if (parameter.ToString() == "Comp")
{
string name = coll[0].Company;
foreach (var c in coll)
{
if (c.Company != name)
return null;
else continue;
}
return name;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter.ToString() == "FName")
{
foreach (var c in mycollection)
{
c.FirstName = value.ToString();
}
return mycollection;
}
else
if (parameter.ToString() == "SName")
{
foreach (var c in mycollection)
{
c.SecondName = value.ToString();
}
return mycollection;
}
else
if (parameter.ToString() == "Comp")
{
foreach (var c in mycollection)
{
c.Company = value.ToString();
}
return mycollection;
}
return null;
}
}
I am having an absolute headache figuring this out. I badly need some help with this.
I have a listbox populated with items called with a public static void RSS feed class. Once the listbox populates with the databound items, I click on an item and it passes it through to my pivot page. However, when I flick left or right, all I get is the same image. That is my problem, and what I would like to have happen is if the user flicks left, it loads the previous RSS image. I would like it to also go to the next picture if the If the user scrolls right.
The community has been helpful in providing links to some things, or saying to not use the listbox, etc. However while I am new to all of this, I would just like concrete help with the code i have to achieve what I have in mind. It's nothing personal -- I just need to take babysteps with this before I get worked up with other things I have no clue about.
Here is all my relevant code.
Page 1 Xaml:
<ListBox x:Name="listbox" HorizontalContentAlignment="Stretch" ItemsSource="{Binding items}" SelectionChanged="listbox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Stretch="Fill" Height="60" Width="85" Source="{Binding Url}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Page1 C# Code Behind:
namespace Imaged
{
public partial class UserSubmitted : PhoneApplicationPage
{
private const string Myrssfeed = "http://feeds.bbci.co.uk/news/rss.xml";
public UserSubmitted()
{
InitializeComponent();
//This next function calls the RSS service, and returns the (items) and binds it to
//{listbox.ItemsSource = items;}. I am unable to reference the count of the items, or
//the array of it for some reason? The images load once the page loads.
RssService.GetRssItems(Myrssfeed, (items) => { listbox.ItemsSource = items; }, (exception) => { MessageBox.Show(exception.Message); }, null);
}
}
}
Once the listbox fills I am now trying to pass the selection by the user to a pivot page. I want that same image to show up in the pivot, and when the user pivots left or right, it shows the previous image or next image in the collection.
The Pivot Page I am trying to pass this to, XAML:
<Grid x:Name="LayoutRoot" Background="Transparent">
<!--Pivot Control-->
<controls:Pivot Title="{Binding Title}">
<!--Pivot item one-->
<controls:PivotItem x:Name="item1">
<Image Source="{Binding Url}"/> <!--I take it this is causing the pics to be the same?-->
</controls:PivotItem>
<!--Pivot item two-->
<controls:PivotItem x:Name="item2">
<Image Source="{Binding Url}"/>
</controls:PivotItem>
<!--Pivot item three-->
<controls:PivotItem x:Name="item3">
<Image Source="{Binding Url}"/>
</controls:PivotItem>
</controls:Pivot>
</Grid>
The RSS Service Class being called:
namespace WindowsPhone.Helpers
{
public class RssService
{
public static void GetRssItems(string rssFeed, Action<IList<RssItem>> onGetRssItemsCompleted = null, Action<Exception> onError = null, Action onFinally = null)
{
WebClient webClient = new WebClient();
// register on download complete event
webClient.OpenReadCompleted += delegate(object sender, OpenReadCompletedEventArgs e)
{
try
{
// convert rss result to model
IList<RssItem> rssItems = new List<RssItem>();
Stream stream = e.Result;
XmlReader response = XmlReader.Create(stream);
{
SyndicationFeed feeds = SyndicationFeed.Load(response);
foreach (SyndicationItem f in feeds.Items)
{
RssItem rssItem = new RssItem(f.Title.Text, f.Summary.Text, f.PublishDate.ToString(), f.Links[0].Uri.AbsoluteUri);
rssItems.Add(rssItem);
}
}
// notify completed callback
if (onGetRssItemsCompleted != null)
{
onGetRssItemsCompleted(rssItems);
}
}
finally
{
// notify finally callback
if (onFinally != null)
{
onFinally();
}
}
};
webClient.OpenReadAsync(new Uri(rssFeed));
}
}
}
and finally the RSSItem Class:
namespace WindowsPhone.Helpers
{
public class RssItem
{
public RssItem(string title, string summary, string publishedDate, string url)
{
Title = title;
Summary = summary;
PublishedDate = publishedDate;
Url = url;
// Get plain text from html
PlainSummary = HttpUtility.HtmlDecode(Regex.Replace(summary, "<[^>]+?>", ""));
}
public string Title { get; set; }
public string Summary { get; set; }
public string PublishedDate { get; set; }
public string Url { get; set; }
public string PlainSummary { get; set; }
}
}
Disclaimer: I don't think that binding this many items to a Pivot control is necessarily the right thing to do. Your mileage may vary, but I think a more virtualized solution would be more efficient. For my tests, it seemed to perform OK, but my little voice tells me that there be dragons here...
I recreated your project to the best of my ability and made some enhancements to get it to do what you wanted. Basically, the trick was using a ViewModel that was shared between both the main list page (UserSubmitted.xaml) and the page with the Pivot items on it (PivotPage1.xaml). By setting both page's DataContext property to the same object, we were able to bind both lists to the same source, thus eliminating the need to pass anything around.
In App.xaml.cs:
public static ViewData ViewModel { get; private set; }
private void Application_Launching(object sender, LaunchingEventArgs e)
{
// note: you should properly Tombstone this data to prevent unnecessary network access
ViewModel = new ViewData();
}
Here is how ViewData is defined:
public class ViewData : INotifyPropertyChanged
{
private string _FeedTitle;
private RssItem _SelectedItem = null;
private ObservableCollection<RssItem> _feedItems = new ObservableCollection<RssItem>();
private const string MyRssfeed = "http://feeds.bbci.co.uk/news/rss.xml";
public ViewData()
{
RssService.GetRssItems(
MyRssfeed,
(title, items) =>
{
App.Current.RootVisual.Dispatcher.BeginInvoke(() =>
{
FeedTitle = title;
FeedItems = new ObservableCollection<RssItem>(items);
});
},
(exception) =>
{
MessageBox.Show(exception.Message);
},
null);
}
public ObservableCollection<RssItem> FeedItems
{
get { return _feedItems; }
set
{
if (_feedItems == value)
return;
_feedItems = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("FeedItems"));
}
}
public string FeedTitle
{
get { return _FeedTitle; }
set
{
if (_FeedTitle == value)
return;
_FeedTitle = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("FeedTitle"));
}
}
public RssItem SelectedItem
{
get { return _SelectedItem; }
set
{
if (_SelectedItem == value)
return;
_SelectedItem = value;
NotifyPropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
PropertyChanged(sender, args);
}
}
Once this is established, it's relatively easy to wire up both page's data context properties to App.ViewModel.
Last item was the scrolling and positioning of the selected item when navigating. When you select an item from the list page, the SelectedItem property of the shared ViewModel is bound to the SelectedItem property on the ListBox. After navigation to the details page, we have to find the selected item in the pivot and make it visible:
public PivotPage1()
{
InitializeComponent();
Loaded += (sender, e) =>
{
this.DataContext = App.ViewModel;
var selectedItem = App.ViewModel.SelectedItem;
var pi = ItemPivot.Items.First(p => p == selectedItem);
ItemPivot.SelectedItem = pi;
};
}
Setting the SelectedItem property of the Pivot control scrolls the pivot to the proper item and makes it visible.
The full sample is posted at http://chriskoenig.net/upload/imaged.zip if you want to see it in action.
If I got you correctly, you need to bind listbox in following way:
<ListBox ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}" />
And then bind Pivot in same way:
<Pivot ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}" />
Try the following for the pivot (based on Alex's code)
<Pivot ItemsSource="{Binding items}" SelectedItem="{Binding SelectedFeed, Mode=TwoWay}">
<Pivot.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Url}"/>
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
It assumes on the pivot page DataContext there is the same object "items" providing access to all the feeditems, and a property SelectedFeed which (as Alex mentioned) supports INotifyPropertyChanged