I've spent lots of hours with this problem.
I have a class with data:
public class User : INotifyPropertyChanged
{
private int _key;
private string _fullName;
private string _nick;
public int Key
{
get{return _key;}
set { _key = value; NotifyPropertyChanged("Key"); }
}
public string Nick
{
get { return _nick; }
set { _nick = value; NotifyPropertyChanged("Nick"); }
}
public string FullName
{
get { return _fullName; }
set { _fullName = value; NotifyPropertyChanged("FullName"); }
}
public User()
{
Nick = "nickname";
FullName = "fullname";
}
public User(String nick, String name, int key)
{
Nick = nick;
FullName = name;
}
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return string.Format("{0} {1}, {2}", Key, Nick, FullName);
}
}
Next I have a class with observablecollection of userClass class:
public class UserList : ObservableCollection<UserList>
{
public UserList (){}
~UserList ()
{
//Serialize();
}
public void Serialize(ObservableCollection<UserList> usersColl)
{
FileStream fs = new FileStream("DataFile.dat", FileMode.Create);
BinaryFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(fs, usersColl);
}
catch (SerializationException e)
{
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
}
finally
{
fs.Close();
}
}
public void Deserialize()
{
FileStream fs = new FileStream("DataFile.dat", FileMode.Open);
try
{
BinaryFormatter formatter = new BinaryFormatter();
//users = (Hashtable) formatter.Deserialize(fs);
//usersColl = (ObservableCollection<userClass>)formatter.Deserialize(fs);
}
catch (SerializationException e)
{
MessageBox.Show(" Error: " + e.Message);
throw;
}
finally
{
fs.Close();
}
}
}
In fact, after lots of testing an editing, big part of code doesn't work, like serialization. But it is not necessary for data binding and binding is what i am solving now.
So i have this collection and want to bind it to listBox.
I tried several ways, but haven't got it to work.
The last one I tried gave me the write error:
The resource 'users' cannot be resolved.
<ListBox Grid.Column="0" Name="userViewLeft" ItemsSource="{Binding Source={StaticResource users} }" />
Some points to be noted
Make Properties public and not private.
Make Variables private.
Follow Naming Conventions and don't append class behind the class.
ItemsSource you supply should be as per scope of the data, In my example the userlist in class scope and I have provided the ItemSource on Window Loaded event.
Here is the an complete example code, In this I have nested the Grid Control inside ListBox because later on you can change the ListBox property for VirtualizingStackPanel.
So that it would give huge performance gain when you have heavy updates on the list.
Also you can use BindingList which is in my opinion better than ObservableCollection performance wise.
User class:
public class User : INotifyPropertyChanged
{
private int _key;
private string _fullName;
private string _nick;
public int Key
{
get { return _key; }
set { _key = value; NotifyPropertyChanged("Key"); }
}
public string NickName
{
get { return _nick; }
set { _nick = value; NotifyPropertyChanged("NickName"); }
}
public string Name
{
get { return _fullName; }
set { _fullName = value; NotifyPropertyChanged("Name"); }
}
public User(String nick, String name, int key)
{
this.NickName = nick;
this.Name = name;
this.Key = key;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return string.Format("{0} {1}, {2}", Key, NickName, Name);
}
}
User List class:
public class Users : ObservableCollection<User>
{
public Users()
{
Add(new User("Jamy", "James Smith", Count));
Add(new User("Mairy", "Mary Hayes", Count));
Add(new User("Dairy", "Dary Wills", Count));
}
}
XAML:
<Grid>
<Button Content="Start" Height="23" HorizontalAlignment="Left" Margin="416,12,0,0" x:Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<ListBox x:Name="UserList" HorizontalContentAlignment="Stretch" Margin="12,41,12,12">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Key}" Margin="3" Grid.Column="0" />
<TextBlock Text="{Binding NickName}" Margin="3" Grid.Column="1" />
<TextBlock Text="{Binding Name}" Margin="3" Grid.Column="2" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
XAML Code behind:
public partial class MainWindow : Window
{
public static Users userslist = new Users();
DispatcherTimer timer = new DispatcherTimer();
public MainWindow()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
timer.Interval = DateTime.Now.AddSeconds(10) - DateTime.Now;
timer.Tick += new EventHandler(timer_Tick);
UserList.ItemsSource = userslist;
}
void timer_Tick(object sender, EventArgs e)
{
userslist.Add(new User("Jamy - " + userslist.Count, "James Smith", userslist.Count));
userslist.Add(new User("Mairy - " + userslist.Count, "Mary Hayes", userslist.Count));
userslist.Add(new User("Dairy - " + userslist.Count, "Dary Wills", userslist.Count));
}
private void button1_Click(object sender, RoutedEventArgs e)
{
if (button1.Content.ToString() == "Start")
{
button1.Content = "Stop";
timer.Start();
}
else
{
button1.Content = "Start";
timer.Stop();
}
}
}
You need to do 2 things:
Firstly, set the DataContext of whatever element (Window/UserControl/whatever) contains your ListBox to an object that looks like:
public class ViewModel
{
public ViewModel() { this.users = new userListClass(); }
public userListClass users { get; private set; }
}
This is your view model, and it is what you want to bind to.
Secondly, change your binding to ItemsSource="{Binding Path=users}". This translates into "set the value of my ItemsSource property to the value of the property users on this.DataContext. Because the DataContext is inherited from the parent, and you set this to the ViewModel class above, your ListBox will now display your user list.
Related
I have got a View who's DataContext is set to an Employee.
Further, the view uses a BindingGroup and Validation Rules.
At last the view has got 2 Buttons: Save and Cancel
Save: Validate the users input and in case of success, save the changes.
Cancel: Rollback the user input and restore the original values.
Until this point it works fine.
Now the last requirement and the problem:
For a better User Experience i would like to enable the save Button when the user begins to change data.
To achieve this, I bind the IsDirty Property of the BindingGroup to the Enabled Property of the Button.
Unfortunately it doesn't work. The binding seems to be correct, but the user interface does not recognize the change of IsDirty.
Who can i solve this problem?
My Model:
public class EmployeeModel:ModelBase
{
private int _nr;
private string _firstname;
private string _lastname;
public int Nr
{
get
{
return _nr;
}
set
{
_nr = value;
OnChanged(nameof(Nr));
}
}
public string Firstname
{
get
{
return _firstname;
}
set
{
_firstname = value;
OnChanged(nameof(Firstname));
}
}
public string Lastname
{
get
{
return _lastname;
}
set
{
_lastname = value;
OnChanged(nameof(Lastname));
}
}
}
ModelBase:
public class ModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
ValidationRule:
public class EmployeeValidationRule:ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
BindingGroup bindingGroup = (BindingGroup)value;
if (bindingGroup.Items.Count == 2)
{
EmployeeModel employee = (EmployeeModel)bindingGroup.Items[1];
string firstname = (string)bindingGroup.GetValue(employee, "Firstname");
string lastname = (string)bindingGroup.GetValue(employee, "Lastname");
if (firstname.Length == 0)
return new ValidationResult(false, "Firstname can not be empty.");
if (lastname.Length == 0)
return new ValidationResult(false, "Lastname can not be empty.");
}
return ValidationResult.ValidResult;
}
}
My ViewModel:
public class EmployeeViewModel
{
private EmployeeModel _employeeModel;
public EmployeeModel Employee
{
get
{
return _employeeModel;
}
set
{
_employeeModel = value;
}
}
public EmployeeViewModel()
{
LoadData();
}
private void LoadData()
{
//Employee = (from e in _context.Employee
// where e.Nr == 158
// select e).FirstOrDefault();
Employee = new EmployeeModel() { Firstname = "Billy", Lastname = "Wilder" };
}
public void Save()
{
//_context.SaveChanges();
}
}
At last the View:
<Window x:Class="WpfApplication3_Validation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication3_Validation"
xmlns:vm="clr-namespace:WpfApplication3_Validation.ViewModel"
xmlns:vr="clr-namespace:WpfApplication3_Validation.ValidationRules"
mc:Ignorable="d"
Title="Employee" Height="250" Width="525"
Validation.ValidationAdornerSite="{Binding ElementName=lbErrors}" Loaded="Window_Loaded">
<Window.DataContext>
<vm:EmployeeViewModel/>
</Window.DataContext>
<Window.BindingGroup>
<BindingGroup x:Name="MyBindingGroup">
<BindingGroup.ValidationRules>
<vr:EmployeeValidationRule/>
</BindingGroup.ValidationRules>
</BindingGroup>
</Window.BindingGroup>
<Grid x:Name="gridMain">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Nr:"/>
<TextBlock Grid.Column="1" Text="{Binding Employee.Nr}"/>
<Label Grid.Row="1" Content="Vorname:" Target="{Binding ElementName=tbFirstname}"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="tbFirstname" Text="{Binding Employee.Firstname}"/>
<Label Grid.Row="2" Content="Nachname:" Target="{Binding ElementName=tbLastname}"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="tbLastname" Text="{Binding Employee.Lastname}"/>
<Label Grid.Row="4" Grid.Column="0" x:Name="lbErrors" Content="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)[0].ErrorContent}"
Foreground="Red" FontWeight="Bold"/>
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock x:Name="tbIsDirty"/>
<Button x:Name="btn1" Content="IsDirty?" Click="btn1_Click"/>
<Button x:Name="btnSave" Content="Save1" Click="btnSave_Click" />
<Button x:Name="btnSave1" Content="Save2" Click="btnSave_Click" IsEnabled="{Binding ElementName=MyBindingGroup, Path=IsDirty}"/>
<Button x:Name="btnCancel" Content="Cancel" Click="btnCancel_Click"/>
</StackPanel>
</Grid>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.MyBindingGroup.BeginEdit(); // Not really needed?
}
private void btnSave_Click(object sender, RoutedEventArgs e)
{
if (this.BindingGroup.CommitEdit())
{
EmployeeViewModel vm = (EmployeeViewModel)this.DataContext;
vm.Save();
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
this.BindingGroup.CancelEdit();
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
tbIsDirty.Text = BindingGroup.IsDirty.ToString();
}
}
Due to the fact that BindingGroup.IsDirty does not Implement INotifyPropertyChanged, it's not a useful source for this type of databinding.
Possible solution:
- Implementing INotifyPropertyChanged in the view
- Creating a own IsDirty in the view, using INotifyPropertyChanged
- Adding an event handler for KeyUp, which sets my IsDirty in case of BindingGroup.IsDirty.
- Binding of Enabled to the new Property
Disadvantage: Need if implementation of INotifyPropertyChanged in the view.
Advantage: It works.
CodeBehind of View:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
private bool _isDirty;
public bool IsDirty
{
get
{
return _isDirty;
}
set
{
_isDirty = value;
OnChanged(nameof(IsDirty));
}
}
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.MyBindingGroup.BeginEdit(); // Not really needed?
gridMain.KeyUp += GridMain_KeyUp;
}
private void GridMain_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (this.MyBindingGroup.IsDirty)
{
IsDirty = true;
}
}
private void btnSave_Click(object sender, RoutedEventArgs e)
{
if (this.BindingGroup.CommitEdit())
{
EmployeeViewModel vm = (EmployeeViewModel)this.DataContext;
vm.Save();
IsDirty = false;
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
this.BindingGroup.CancelEdit();
IsDirty = false;
}
}
Further improvements:
Now i moved IsDirty to my ViewModel, so I don't have to implement INPC in the view. Another advantage is, that in this way, Commands can consume the property and finally i don't have to use databinding for the enabled Property, because i get it over the command.
Help me please!!!
I have 3 UserControls
I select user on List Users UC listbox
Then send message from SendMessage UC to Database
when i send message to Db it must refresh my chat listBox in Correspondence UC, but problem is in my ChatWrapper.
PropertyChanged in ChatWrapper is always null, and I can't refresh my ListBox in Correspondence UC with new message
List Users:
public IEnumerable<EmployeesDb> getListNames
{
get { return Db.Instance.EmployeesDbs.ToList(); }
}
static EmployeesDb m_selectedUser;
public static EmployeesDb selectedUser
{
get { return m_selectedUser; }
set
{
if (value != null)
m_selectedUser = value;
Correspondence correspondence = new Correspondence();
correspondence.CorrespondenceChat();
}
}
}
Send Message ( I try to refresh -> SendInfo.FirstOrDefault().RefreshGUI();)
public static DependencyProperty SendInfoProperty =
DependencyProperty.Register(
"SendInfo",
typeof(IEnumerable<ChatWrapper>),
typeof(SendMessage));
public IEnumerable<ChatWrapper> SendInfo
{
get { return GetValue(SendInfoProperty) as IEnumerable<ChatWrapper>; }
set { SetValue(SendInfoProperty, value); }
}
void SendMessageCommandExecute()
{
//...
SendInfo.FirstOrDefault().RefreshGUI();
//...
}
ChatWrapper
public class ChatWrapper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
public void RefreshGUI()
{
FirePropertyChanged("message");
}
public ChatDb chatDb { get; set; }
public string message
{
get
{
return (chatDb != null) ? string.Format("{0} {1}.{2} / {3} / {4}\n{5}",
chatDb.FromEmployeesDb.surname,
chatDb.FromEmployeesDb.name[0],
chatDb.FromEmployeesDb.middleName[0],
chatDb.messageDateTime,
chatDb.computerName,
chatDb.message) : null;
}
}
Correspondence
//...
public partial class Correspondence : UserControl, INotifyPropertyChanged
{
public static DependencyProperty GetCorrespondenceInfoProperty =
DependencyProperty.Register(
"GetCorrespondenceInfo",
typeof(IEnumerable<ChatWrapper>),
typeof(Correspondence),
new PropertyMetadata(OnChanged));
public IEnumerable<ChatWrapper> GetCorrespondenceInfo
{
get { return GetValue(GetCorrespondenceInfoProperty) as IEnumerable<ChatWrapper>; }
set { SetValue(GetCorrespondenceInfoProperty, value); }
}
static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var me = d as Correspondence;
me.chat = me.GetCorrespondenceInfo;
}
ICollectionView m_CollectionView;
public static IEnumerable<ChatWrapper> m_chat;
public IEnumerable<ChatWrapper> chat
{
get { return m_chat; }
set
{
m_chat = value;
if (ListUsers.selectedUser != null)
CorrespondenceChat();
FirePropertyChanged("chat");
}
}
public void CorrespondenceChat()
{
if (m_chat == null)
return;
m_CollectionView = CollectionViewSource.GetDefaultView(m_chat);
//...
FirePropertyChanged("chat");
}
XAML of Correspondence (refresh
<Grid>
<ListBox x:Name="correspondenceListBox" ItemsSource="{Binding chat, RelativeSource={RelativeSource AncestorType={x:Type local:Correspondence}}}"
Height="auto" Grid.Row="0" Grid.Column="1" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding message}" TextWrapping="Wrap" Width="auto"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I tried to write
public event PropertyChangedEventHandler PropertyChanged = delegate { };
PropertyChanged is no longer null, but it's still not updated
Am trying to bind 2 items to a xaml page.One is an observable collection of services the other is an instance of expert item.Am getting an error A first chance exception of type 'System.ArgumentException' occurred in myapp.Client.DLL when I try to raise an INotifyProperty changed for the expert item.
SummaryPage.xaml
//some fields ommitted for brevity Expert
<TextBlock Text="Full Name" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Expert.name,Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Center"/>
<TextBlock Text="Age" Grid.Row="1" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Expert.age}" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"/>
<TextBlock Text="National ID:" Grid.Row="2" HorizontalAlignment="Left"/>
//observable list services
<ListView ItemsSource="{Binding Services}"
IsItemClickEnabled="False"
ScrollViewer.VerticalScrollMode="Enabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
TabIndex="1" Header="Service Request">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Cost}" Grid.Column="1" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
SummaryPageViewModel.cs
public class SummaryPageViewModel : ViewModel, ISummaryPageViewModel
{
public ICommand ConfirmCommand { get; private set; }
public ExpertItem Expert { get { return this.GetValue<ExpertItem>(); } set { this.SetValue(value); } }
public ObservableCollection<ServiceItem> Services { get { return this.GetValue<ObservableCollection<ServiceItem>>(); } set { this.SetValue(value); } }
public SummaryPageViewModel()
{
}
public override void Initialize(IViewModelHost host)
{
base.Initialize(host);
// setup...
this.Services = new ObservableCollection<ServiceItem>();
this.Expert = new ExpertItem();
this.ConfirmCommand = new DelegateCommand((args) => GetSelected(args as CommandExecutionContext));
}
public override async void Activated(object args)
{
base.Activated(args);
var conn = Myapp.GetSystemDatabase();
using (this.EnterBusy())
{
IEnumerable<ServiceItem> servs = await conn.Table<ServiceItem>().ToListAsync();
foreach (ServiceItem s in servs)
this.Services.Add(s);
IEnumerable<ExpertItem> exps = await ExpertItem.GetAllFromCacheAsync();
this.Expert = await conn.Table<ExpertItem>().Where(v => v.NativeId == 3).FirstOrDefaultAsync();
}
}
ModelItem.cs
public abstract class ModelItem : INotifyPropertyChanged
{
private Dictionary<string, object> Values { get; set; }
protected ModelItem()
{
this.Values = new Dictionary<string, object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected T Get
Value<T>([CallerMemberName] string name = null)
{
if (this.Values.ContainsKey(name))
return (T)this.Values[name];
else
return default(T);
}
protected void SetValue(object value, [CallerMemberName] string name = null)
{
// set...
this.Values[name] = value;
// notify...
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(name));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, e);//error raised here e- property name expert.Value does not fall in expected range
}
}
ExpertItem.cs
//code omitted for brevity
public class ExpertItem : ModelItem
{
public int Id { get; set; }
public int NativeId { get { return this.GetValue<int>(); } set { this.SetValue(value); } }
public string name { get { return this.GetValue<string>(); } set { this.SetValue(value); } }
public int age { get { return this.GetValue<int>(); } set { this.SetValue(value); } }
public string email { get { return this.GetValue<string>(); } set { this.SetValue(value); } }
}
stacktrace
at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e)
at MFundi.Client.ModelItem.OnPropertyChanged(PropertyChangedEventArgs e)
at MFundi.Client.ModelItem.SetValue(Object value, String name)
at MFundi.Client.SummaryPageViewModel.set_Expert(ExpertItem value)
at MFundi.Client.SummaryPageViewModel.<Activated>d__b.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__3(Object state)
I don't if I am doing something wrong or if there is something I am missing but the INotifyPropertyChanged works when I do it with compile time binding and doesn't work when I do it with traditional binding.
public class Students : INotifyPropertyChanged
{
private string name;
private string surname;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Name
{
get { return name; }
set
{
if(value != name)
{
name = value;
NotifyPropertyChanged();
}
}
}
public string Surname
{
get { return surname; }
set
{
if (value != surname)
{
surname = value;
NotifyPropertyChanged();
}
}
}
public Students()
{
Name = "John";
Surname = "Smith";
}
}
MainPage.xaml
<Page.DataContext>
<local:Students/>
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="0,200,0,0">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Surname}"/>
<Button Content="Change Name" Click="change_name"/>
<Button Content="Change Surname" Click="change_surname"/>
</StackPanel>
</Grid>
MianPage.xaml.cs
public sealed partial class MainPage : Page
{
Students st;
public MainPage()
{
this.InitializeComponent();
st = new Students();
}
private void change_name(object sender, RoutedEventArgs e)
{
st.Name = "MM";
}
private void change_surname(object sender, RoutedEventArgs e)
{
st.Surname = "SS";
}
}
I am really confused, because when you bind with compile time binding, it works fine. What's going on?
I don't see any place in which you are setting the current DataContext to your object.
public MainPage()
{
this.InitializeComponent();
st = new Students();
this.DataContext = st;
}
OR: You are setting a datacontext in your XAML, but you aren't referencing it.
<Page.DataContext>
<local:Students/>
</Page.DataContext>
You would need to reference that object from code if you intend to use it.
private void change_name(object sender, RoutedEventArgs e)
{
((Students)this.DataContext).Name = "MM";
}
I had a situation very similar to the author (if not the same) and only thing I did to fix it is I made my view model a public property. In case of the example above, it would be Students st; changed to public Students st {get; set;}
I have the following XAML for a list of data items:
<phone:LongListSelector x:Name="Port_SummaryList" ItemsSource="{Binding PortList}" ItemTemplate="{StaticResource PortfolioDataTemplate}"/>
The template is defined as this:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="PortfolioDataTemplate">
<Grid d:DesignHeight="91.5" d:DesignWidth="439.875" Height="82">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="31*"/>
<ColumnDefinition Width="19*"/>
<ColumnDefinition Width="19*"/>
<ColumnDefinition Width="19*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15*"/>
<RowDefinition Height="15*"/>
<RowDefinition Height="15*"/>
</Grid.RowDefinitions>
<TextBlock x:Name="PortfolioName" HorizontalAlignment="Left" Height="92" TextWrapping="Wrap" Text="{Binding Name}" VerticalAlignment="Top" Width="155" Grid.RowSpan="2" Margin="0,0,0,-10"/>
<TextBlock x:Name="NAV" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" Height="31" TextWrapping="Wrap" Text="{Binding NAV, StringFormat='{}{0:C}'}" VerticalAlignment="Top" Width="95" Margin="0,-1,0,0"/>
<TextBlock x:Name="CostBasis" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" Height="30" TextWrapping="Wrap" Text="{Binding Cost,StringFormat='{}{0:C}'}" VerticalAlignment="Top" Width="95" />
</Grid>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
and in my ViewModel I have this:
private TrulyObservableCollection<PortfolioModel> _PortList;
public TrulyObservableCollection<PortfolioModel> PortList
{
get { return _PortList; }
set
{
_PortList = value;
_PortList.CollectionChanged += _PortList_CollectionChanged;
RaisePropertyChanged("PortList");
}
}
void _PortList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("PortList");
}
The class "TrulyObservableCollection<>" is from this SO post.
The class "PortfolioModel" is defined as this:
public class PortfolioModel : INotifyPropertyChanged
{
public string Name { get; set; }
public DateTime Created { get; set; }
public int Id { get; set; }
public TrulyObservableCollection<CashModel> Cashflow;
public TrulyObservableCollection<HoldingModel> Positions;
public float Cost
{
get
{
float total_cost = 0.0f;
foreach (HoldingModel holding in Positions)
{
total_cost += holding.Share * holding.CostBasis;
}
return total_cost;
}
private set { ;}
}
//Numbers that are calculated with other variables, listed here for databinding purposes
public float NAV
{
get
{
float acc = 0.0f;
foreach (HoldingModel hm in Positions)
{
acc += hm.CurrentPrice * hm.Share;
}
foreach (CashModel cm in Cashflow)
{
acc += cm.Amount;
}
return acc;
}
set { ;}
}
public float DailyPercent { get; set; }
public float OverallPercent { get; set; }
public PortfolioModel()
{
Cashflow = new TrulyObservableCollection<CashModel>();
Cashflow.CollectionChanged += Cashflow_CollectionChanged;
Positions = new TrulyObservableCollection<HoldingModel>();
Positions.CollectionChanged += Positions_CollectionChanged;
}
void Positions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Positions");
NotifyPropertyChanged("NAV");
}
void Cashflow_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Cashflow");
NotifyPropertyChanged("NAV");
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The class "HoldingModel" is defined as this:
public class HoldingModel : INotifyPropertyChanged
{
private string _Ticker;
public string Ticker
{
get { return _Ticker; }
set { if (value != _Ticker) { _Ticker = value; NotifyPropertyChanged("Ticker"); } }
}
private string _CompanyName;
public string CompanyName
{
get { return _CompanyName; }
set { if (value != _CompanyName) { _CompanyName = value; NotifyPropertyChanged("CompanyName"); } }
}
private int _Share;
public int Share
{
get { return _Share; }
set { if (value != _Share) { _Share = value; NotifyPropertyChanged("Share"); } }
} //negative means short
public string LongShort
{
get { if (Share > 0) return "LONG"; else return "SHORT"; }
}
private float _Value;
public float Value
{
get { return _Value; }
set { if (value != _Value) { _Value = value; NotifyPropertyChanged("Value"); } }
}
public float Cost
{
get { return Share * CostBasis; }
set { ;}
}
private float _CostBasis;
public float CostBasis
{
get { return _CostBasis; }
set { if (value != _CostBasis) { _CostBasis = value; NotifyPropertyChanged("CostBasis"); } }
}
private float _RealizedGain;
public float RealizedGain
{
get { return _RealizedGain; }
set { _RealizedGain = value; NotifyPropertyChanged("RealizedGain"); }
}
private float _CurrentPrice;
public float CurrentPrice
{
get { return _CurrentPrice; }
set
{
_CurrentPrice = value;
NotifyPropertyChanged("CurrentPrice");
}
}
private float _CurrentChange;
public float CurrentChange
{
get { return _CurrentChange; }
set { _CurrentChange = value; NotifyPropertyChanged("CurrentChange"); }
}
ObservableCollection<TradeModel> Trades;
public HoldingModel()
{
Trades = new ObservableCollection<TradeModel>();
}
public void AddTrade(TradeModel trade)
{
//Order can't change, since RealizedGain relies on CostBasis and Share, CostBasis relies on Share
UpdateRealizedGain(trade);
UpdateCostBasis(trade);
Share += trade.Share;
trade.PropertyChanged += PropertyChanged;
Trades.Add(trade);
}
private void UpdateCostBasis(TradeModel trade)
{
if (trade.Share + Share == 0)
{
CostBasis = 0;
return;
}
float cost = CostBasis * Share;
cost += trade.Price * trade.Share;
CostBasis = cost / (Share + trade.Share);
}
private void UpdateRealizedGain(TradeModel trade)
{
if (trade.Share * Share > 0) return; //No realized gain on add-on
if (Math.Abs(trade.Share) > Math.Abs(Share))
{
RealizedGain += Share * (trade.Price - CostBasis);
}
else
{
RealizedGain += trade.Share * (trade.Price - CostBasis);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
//Debug.WriteLine("symbol_user got property {0} changed, bubbling up", propertyName);
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
What I wanted to do is that, every time I update the CurrentPrice property in HoldingModel, I want to see the NAV property in PortfolioModel change, and reflect that in the view. I tried all I can but still unable to achieve that. Is there anything that I'm missing? Any help is appreciated
I've also noticed some problems with LongListSelector and ObservableCollection. I've posted it here:
Long List Selector Observable Collection and Visual Tree - problems?
Please check in your project something like this: leave the Page with back button and the reenter the page with LLS - if it's correctly displayed that mean we have the same problems, and I think it's the problem with LLS and we have to wait for WP 8.1. I assume that LLS is not correctly Updated (VisualTree doesn't change), because when I use normal ListBox everything works perfect.
Try to use ListBox (as you don't have grouping):
<ListBox x:Name="Port_SummaryList" ItemsSource="{Binding PortList}" ItemTemplate="{StaticResource PortfolioDataTemplate}"/>
If you don't see changes you can try call (in my project that function didn't worj with LLS but with LisBox works fine):
Port_SummaryList.UpdateLayout();
Try explicitly specifying Mode=OneWay in the NAV binding.
Text="{Binding NAV, Mode=OneWay, StringFormat='{}{0:C}'}"
I just had a case where the Mode behaved like it was defaulting to the Mode=OneTime. After explicitly setting Mode=OneWay, my data changes started to display. The BindingMode Enumeration documentation here suggests Mode=OneWay is implied. Recent experience suggests that may not always be the case.