I am working on a windows phone application that uses a local database and MVVM. My listbox is not showing the WeatherList that it is binded to. I am adding a single WeatherItem to the local database in the App.xaml.cs file and it's being added successfully, but it is not showing up in the listbox when running the app. I would really appreciate any help. I have the following setup:
MainPage.xaml:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="ForecastListItemTemplate">
<StackPanel Orientation="Horizontal" Margin="0, 400, 0, 0" Width="480" Height="100">
<StackPanel Width="190" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="{Binding Path=DayOfWeek}" FontFamily="Segoe WP" FontWeight="ExtraBold" FontSize="36" Foreground="#545d61"></TextBlock>
<TextBlock HorizontalAlignment="Center" Text="{Binding Path=ItemDay}" FontFamily="Segoe WP Light" FontSize="36" Foreground="#545d61"></TextBlock>
</StackPanel>
<Image Height="100" Width="100" Source="{Binding Path=ImageSource}"></Image>
<StackPanel Width="190" VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<TextBlock Text="↑" FontFamily="Segoe WP" FontWeight="ExtraBold" FontSize="36" Foreground="#545d61"></TextBlock>
<TextBlock HorizontalAlignment="Center" Margin="4, 0, 4, 0" Text="{Binding Path=High}" FontFamily="Segoe WP" FontWeight="ExtraBold" FontSize="36" Foreground="#0da5d0"></TextBlock>
<TextBlock Text="↓" FontFamily="Segoe WP" FontWeight="ExtraBold" FontSize="36" Foreground="#545d61"></TextBlock>
<TextBlock HorizontalAlignment="Center" Margin="4, 0, 4, 0" Text="{Binding Path=Low}" FontFamily="Segoe WP" FontWeight="ExtraBold" FontSize="36" Foreground="#0da5d0"></TextBlock>
</StackPanel>
<TextBlock HorizontalAlignment="Center" Text="{Binding Path=Condition}" FontFamily="Segoe WP Light" FontSize="24" Foreground="#545d61"></TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="#e1e4e4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox
x:Name="WeatherListBox"
ItemsSource="{Binding WeatherList}"
Margin="0, 400, 0, 96"
Width="480"
ItemTemplate="{StaticResource ForecastListItemTemplate}">
</ListBox>
</Grid>
WeatherViewModel.cs
public class WeatherViewModel : INotifyPropertyChanged
{
// LINQ to SQL data context for the local database.
private ToDoDataContext weatherDB;
// Class constructor, create the data context object.
public WeatherViewModel()
{
weatherDB = new ToDoDataContext();
}
// All to-do items.
private ObservableCollection<WeatherItem> _weatherList;
public ObservableCollection<WeatherItem> WeatherList
{
get { return _weatherList; }
set
{
_weatherList = value;
NotifyPropertyChanged("WeatherList");
}
}
// Write changes in the data context to the database.
public void SaveChangesToDB()
{
weatherDB.SubmitChanges();
}
// Query database and load the collections and list used by the pivot pages.
public void LoadCollectionsFromDatabase()
{
// Specify the query for all to-do items in the database.
var weatherItemsInDB = from WeatherItem weather in weatherDB.Forecasts
select weather;
// Query the database and load all to-do items.
WeatherList = new ObservableCollection<WeatherItem>(weatherItemsInDB);
}
// Add a to-do item to the database and collections.
public void AddWeatherItem(WeatherItem newWeatherItem)
{
// Add a to-do item to the data context.
weatherDB.Forecasts.InsertOnSubmit(newWeatherItem);
// Save changes to the database.
weatherDB.SubmitChanges();
// Add a to-do item to the "all" observable collection.
WeatherList.Add(newWeatherItem);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify Silverlight that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
ToDoDataContext.cs
public class ToDoDataContext : DataContext
{
// Pass the connection string to the base class.
public ToDoDataContext()
: base("Data Source=isostore:/AppDB.sdf")
{ }
public ToDoCategory activeCategory;
// Specify a table for the to-do items.
public Table<ToDoItem> Items;
// Specify a table for the categories.
public Table<ToDoCategory> Categories;
//Weather stuff
public WeatherItem currentWeather;
// Specify a table for the to-do items.
public Table<WeatherItem> Forecasts;
}
[Table]
public class WeatherItem : INotifyPropertyChanged, INotifyPropertyChanging
{
// Define ID: private field, public property, and database column.
private int _weatherItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int WeatherItemId
{
get { return _weatherItemId; }
set
{
if (_weatherItemId != value)
{
NotifyPropertyChanging("WeatherItemId");
_weatherItemId = value;
NotifyPropertyChanged("WeatherItemId");
}
}
}
// Define item name: private field, public property, and database column.
private string _itemDay;
[Column]
public string ItemDay
{
get { return _itemDay; }
set
{
if (_itemDay != value)
{
NotifyPropertyChanging("ItemDay");
_itemDay = value;
NotifyPropertyChanged("ItemDay");
}
}
}
private string _dayOfWeek;
[Column]
public string DayOfWeek
{
get { return _dayOfWeek; }
set
{
if (_dayOfWeek != value)
{
NotifyPropertyChanging("DayOfWeek");
_dayOfWeek = value;
NotifyPropertyChanged("DayOfWeek");
}
}
}
// Define completion value: private field, public property, and database column.
private string _high;
[Column]
public string High
{
get { return _high; }
set
{
if (_high != value)
{
NotifyPropertyChanging("High");
_high = value;
NotifyPropertyChanged("High");
}
}
}
private string _low;
[Column]
public string Low
{
get { return _low; }
set
{
if (_low != value)
{
NotifyPropertyChanging("Low");
_low = value;
NotifyPropertyChanged("Low");
}
}
}
private string _condition;
[Column]
public string Condition
{
get { return _condition; }
set
{
if (_condition != value)
{
NotifyPropertyChanging("Condition");
_condition = value;
NotifyPropertyChanged("Condition");
}
}
}
private string _imageSource;
[Column]
public string ImageSource
{
get { return _imageSource; }
set
{
if (_imageSource != value)
{
NotifyPropertyChanging("ImageSource");
_imageSource = value;
NotifyPropertyChanged("ImageSource");
}
}
}
// Version column aids update performance.
[Column(IsVersion = true)]
private Binary _version;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify that a property changed
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Used to notify that a property is about to change
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
Mainpage.xaml.cs
// Constructor
public MainPage()
{
InitializeComponent();
this.DataContext = App.WeatherViewModel;
}
App.xaml.cs
public partial class App : Application
{
private static ToDoViewModel viewModel;
public static ToDoViewModel ViewModel
{
get { return viewModel; }
}
private static WeatherViewModel weatherViewModel;
public static WeatherViewModel WeatherViewModel
{
get { return weatherViewModel; }
}
/// <summary>
/// Provides easy access to the root frame of the Phone Application.
/// </summary>
/// <returns>The root frame of the Phone Application.</returns>
public PhoneApplicationFrame RootFrame { get; private set; }
/// <summary>
/// Constructor for the Application object.
/// </summary>
public App()
{
// Global handler for uncaught exceptions.
UnhandledException += Application_UnhandledException;
// Standard Silverlight initialization
InitializeComponent();
// Phone-specific initialization
InitializePhoneApplication();
// Show graphics profiling information while debugging.
if (System.Diagnostics.Debugger.IsAttached)
{
// Display the current frame rate counters.
Application.Current.Host.Settings.EnableFrameRateCounter = true;
// Show the areas of the app that are being redrawn in each frame.
//Application.Current.Host.Settings.EnableRedrawRegions = true;
// Enable non-production analysis visualization mode,
// which shows areas of a page that are handed off to GPU with a colored overlay.
//Application.Current.Host.Settings.EnableCacheVisualization = true;
// Disable the application idle detection by setting the UserIdleDetectionMode property of the
// application's PhoneApplicationService object to Disabled.
// Caution:- Use this under debug mode only. Application that disables user idle detection will continue to run
// and consume battery power when the user is not using the phone.
PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
}
// Create the database if it does not exist.
using (ToDoDataContext db = new ToDoDataContext())
{
if (db.DatabaseExists() == false)
{
// Create the local database.
db.CreateDatabase();
// Prepopulate the categories.
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Home" });
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Work" });
db.Categories.InsertOnSubmit(new ToDoCategory { Name = "School" });
db.Forecasts.InsertOnSubmit(new WeatherItem
{
DayOfWeek = "Mon",
ItemDay = "Sept 4",
Condition = "Mostly Sunny",
High = "105",
Low = "88",
ImageSource = "images/Weather/45.png"
});
// Save categories to the database.
db.SubmitChanges();
}
}
// Create the ViewModel object.
viewModel = new ToDoViewModel();
weatherViewModel = new WeatherViewModel();
// Query the local database and load observable collections.
viewModel.LoadCollectionsFromDatabase();
weatherViewModel.LoadCollectionsFromDatabase();
}
Thanks!
Did you check out the output window for data binding errors?
And in the ctor of the main page, is App.WeatherViewModel already not-null?
You add
WeatherListBox.DataContext = WeatherList;
And you modify binding with
ItemsSource="{Binding}"
Related
I have a ViewModel with all the properties that i will need in every sub ViewModel.
It's the first time i try to split commands and viewmodel to multiple files. Last time everything was in the same ViewModel and it was a pain to work with it. Everything shows up as expected but i want to find a way to pass the same data in every viewmodel.
From my GetOrdersCommand, i want to get the HeaderViewModel.SelectedSource property. I didn't find any way to do it without getting a null return or loosing the property data...
I would like to call my GetOrdersCommand from HeaderView button too.
Any tips how i can achieve this ? Perhaps, my design is not good for what i'm trying to do ?
MainWindow.xaml
<views:HeaderView Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding HeaderViewModel}" LoadHeaderViewCommand="{Binding LoadHeaderViewCommand}"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TabItem Header="General">
</TabItem>
<TabItem Header="Orders">
<views:OrderView DataContext="{Binding OrderViewModel}" GetOrdersCommand="{Binding GetOrdersCommand}"/>
</TabItem>
</TabControl>
HeaderView.xaml
<DockPanel>
<ComboBox DockPanel.Dock="Left" Width="120" Margin="4" VerticalContentAlignment="Center" ItemsSource="{Binding SourceList}" SelectedItem="{Binding SelectedSource}" DisplayMemberPath="SourceName"/>
<Button x:Name="btnTest" HorizontalAlignment="Left" DockPanel.Dock="Left" Margin="4" Content="Test"/>
</DockPanel>
HeaderView.xaml.cs
public partial class OrderView : UserControl
{
public ICommand GetOrdersCommand
{
get { return (ICommand)GetValue(GetOrdersCommandProperty); }
set { SetValue(GetOrdersCommandProperty, value); }
}
public static readonly DependencyProperty GetOrdersCommandProperty =
DependencyProperty.Register("GetOrdersCommand", typeof(ICommand), typeof(OrderView), new PropertyMetadata(null));
public OrderView()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
if (GetOrdersCommand != null)
{
GetOrdersCommand.Execute(this);
}
}
}
MainViewModel.cs
private OrderViewModel orderViewModel;
public OrderViewModel OrderViewModel { get; set; } // Getter, setter with OnPropertyChanged
private HeaderViewModel headerViewModel;
public HeaderViewModel HeaderViewModel { get; set; } // Getter, setter with OnPropertyChanged
public MainViewModel()
{
HeaderViewModel = new HeaderViewModel();
OrderViewModel = new OrderViewModel();
}
HeaderViewModel.cs
public ICommand LoadHeaderViewCommand { get; set; }
public HeaderViewModel()
{
LoadHeaderViewCommand = new LoadHeaderViewCommand(this);
}
GetOrdersCommand.cs
public class GetOrdersCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private readonly OrderViewModel _orderViewModel;
public GetOrdersCommand(OrderViewModel orderViewModel)
{
_orderViewModel = orderViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
/* Build Order List according to HeaderViewModel.SelectedSource */
_orderViewModel.Orders = new ObservableCollection<Order>()
{
new Order { ID = 1, IsReleased = false, Name = "Test1"},
new Order { ID = 2, IsReleased = true, Name = "Test2"},
};
}
}
Thanks guys ! I moved my commands to their owning ViewModel as suggested.
I tried MVVVM Light Tools and found about Messenger Class.
I used it to send my SelectedSource (Combobox from HeaderView) from HeaderViewModel to OrderViewModel. Am i suppose to use Messenger class like that ? I don't know, but it did the trick!!!
I thought about moving GetOrdersCommand to OrderViewModel, binding my button command to OrderViewModel, binding SelectedSource as CommandParameter but i didn't know how i was suppose to RaiseCanExecuteChanged when HeaderViewModel.SelectedSource changed... Any advice?
MainWindow.xaml
<views:HeaderView DataContext="{Binding Source={StaticResource Locator}, Path=HeaderVM}" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"/>
<TabControl TabStripPlacement="Bottom" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TabItem Header="General">
</TabItem>
<TabItem Header="Orders">
<views:OrderView DataContext="{Binding Source={StaticResource Locator}, Path=OrderVM}"/>
</TabItem>
</TabControl>
OrderViewModel.cs
private ObservableCollection<Order> _orders;
public ObservableCollection<Order> Orders
{
get { return _orders; }
set
{
if (_orders != value)
{
_orders = value;
RaisePropertyChanged(nameof(Orders));
}
}
}
public OrderViewModel()
{
Messenger.Default.Register<Source>(this, source => GetOrders(source));
}
private void GetOrders(Source source)
{
if (source.SourceName == "Production")
{
Orders = new ObservableCollection<Order>(){
new Order { ID = 1, IsReleased = false, Name = "Production 1" }
};
}
else
{
Orders = new ObservableCollection<Order>(){
new Order { ID = 2, IsReleased = true, Name = "Test 1" }
};
}
}
Part of HeaderViewModel.cs
private Source _SelectedSource;
public Source SelectedSource
{
get { return _SelectedSource; }
set
{
if (_SelectedSource != value)
{
_SelectedSource = value;
RaisePropertyChanged(nameof(SelectedSource));
GetOrdersCommand.RaiseCanExecuteChanged();
}
}
}
private RelayCommand _GetOrdersCommand;
public RelayCommand GetOrdersCommand
{
get
{
if (_GetOrdersCommand == null)
{
_GetOrdersCommand = new RelayCommand(GetOrders_Execute, GetOrders_CanExecute);
}
return _GetOrdersCommand;
}
}
private void GetOrders_Execute()
{
Messenger.Default.Send(SelectedSource);
}
private bool GetOrders_CanExecute()
{
return SelectedSource != null ? true : false;
}
What I'm trying to achieve doesn't sounds like rocket science. What I'm trying to create is a custom control to which I could pass a list of UIElements items directly from XAML so each element could be different and embed different objects (grid / textbox / panel etc ... ).
Here is the xaml code I would like to use:
<wpf:TileListDoubleItem>
<wpf:TileListDoubleItem.FrontItem>
<Grid>
<TextBlock FontFamily="Calibri,Verdana" FontSize="16" FontWeight="Bold" Foreground="White" Text="Hello"></TextBlock>
</Grid>
</wpf:TileListDoubleItem.FrontItem>
<wpf:TileListDoubleItem.BackItem>
<Grid>
<TextBlock FontFamily="Calibri,Verdana" FontSize="16" FontWeight="Bold" Foreground="White" Text="World"></TextBlock>
</Grid>
</wpf:TileListDoubleItem.BackItem>
</wpf:TileListDoubleItem>
And here is my custom control code:
public partial class TileListDoubleItem : UserControl, INotifyPropertyChanged
{
private bool _flipped;
internal bool CanFlip { get { return true; } }
private bool flipped
{
get {
return this._flipped;
}
set {
this._flipped = value;
DisplayItem = this._flipped ? BackItem : FrontItem;
}
}
public ObservableCollection<TileSide> Sides { get; set; }
public ICommand FlipCommand;
public TileListDoubleItem()
{
InitializeComponent();
FlipCommand = new FlipCommand(this);
flipped = false;
}
private UIElement displayItem { get; set; }
public UIElement DisplayItem
{
get { return this.displayItem; }
set {
if (this.displayItem != value)
{
this.displayItem = value;
OnPropertyChanged("DisplayItem");
}
}
}
public void Flip()
{
try
{
flipped = !flipped;
}
catch (Exception ex)
{
throw ex;
}
}
public UIElement FrontItem
{
get { return (UIElement)GetValue(FrontItemProperty); }
set { SetValue(FrontItemProperty, value); }
}
public static readonly DependencyProperty FrontItemProperty =
DependencyProperty.Register("FrontItem", typeof(UIElement), typeof(TileListDoubleItem), new UIPropertyMetadata(null));
public UIElement BackItem
{
get { return (UIElement)GetValue(BackItemProperty); }
set { SetValue(BackItemProperty, value); }
}
public static readonly DependencyProperty BackItemProperty =
DependencyProperty.Register("BackItem", typeof(UIElement), typeof(TileListDoubleItem), new UIPropertyMetadata(null));
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
When I run this, both my FrontItem and BackItem are equal to null and are never set to the UIElement (Grid in this example).
I guess what I'm missing must be very obvious to some people.
Thanks in advance for anyone's help here.
Your properies are set as expected. You can confirm this by for example create two TextBlocks that binds to the properties of your control:
<wpf:TileListDoubleItem x:Name="control">
<wpf:TileListDoubleItem.FrontItem>
<Grid>
<TextBlock FontFamily="Calibri,Verdana" FontSize="16" FontWeight="Bold" Foreground="White" Text="Hello"></TextBlock>
</Grid>
</wpf:TileListDoubleItem.FrontItem>
<wpf:TileListDoubleItem.BackItem>
<Grid>
<TextBlock FontFamily="Calibri,Verdana" FontSize="16" FontWeight="Bold" Foreground="White" Text="World"></TextBlock>
</Grid>
</wpf:TileListDoubleItem.BackItem>
</wpf:TileListDoubleItem>
<TextBlock Text="{Binding FrontItem.Children[0].Text, ElementName=control}" />
<TextBlock Text="{Binding BackItem.Children[0].Text, ElementName=control}" />
Obviously the properties won't be set by the time the constructor of TileListDoubleItem returns. The XAML parser needs to instantiate the object before it can set any of its properties, just like you do when you create an instance of a class yourself.
I have below xaml file (this a piece):
<Grid Opacity="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="ID"/>
<Label Grid.Row="0" Grid.Column="1" Content="Name"/>
<Label Grid.Row="0" Grid.Column="2" Content="Description"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding ID}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Name}"/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Description}"/>
</Grid>
Below the Data Class:
public class Data : INotifyPropertyChanged
{
private string id= string.Empty;
private string name = string.Empty;
private string description = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string ID
{
get
{
return this.id;
}
set
{
if (value != this.id)
{
this.id = value;
NotifyPropertyChanged("ID");
}
}
}
public string Name
{
get
{
return this.name;
}
set
{
if (value != this.name)
{
this.name = value;
NotifyPropertyChanged("Name");
}
}
}
public string Description
{
get
{
return this.description;
}
set
{
if (value != this.description)
{
this.description = value;
NotifyPropertyChanged("Description");
}
}
}
}
Also in xaml.cs I implement INotifyPropertyChanged:
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
Furthermore, in the above xaml I have a button defined as:
<Button Click="btn_Click"/>
and the implementation for that is in xaml.cs as below:
private void btn_Click(object sender, System.Windows.RoutedEventArgs e)
{
(DataContext as MyViewModel).SearchInDb('0303003'); // 0303003 -> this is only an example.
}
On button click a method on MyViewModel class is called, and from there it invokes a query to database to retrieve data using ID = 0303003.
Below MyViewModel class (I show only the method):
public void SearchInDb(string id)
{
// request data to sql server database
// and then populate a Data Class Object with the data retrieved
// from database:
Data = new Data(){
ID = sReader[0].ToString().Trim(),
Name = sReader[1].ToString().Trim(),
Description = sReader[2].ToString().Trim()
};
}
Note: MyViewModel class does not implement INotifyPropertyChanged.
My problem is the following:
After populating a new Data object within above method "SearchInDb", my labels in the grid are not updated, they remain empty.
You need to set the View's DataContext:
View.xaml.cs
public View()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
But there is some other issues in your snippets. In the MVVM way, it is the ViewModel which is supposed to implements INotifyPropertyChanged. Not the data class.
Here is how it is supposed to be:
Data Class
public class Data
{
public string Id {get;set;}
public string Name {get;set;}
public string Description {get; set;}
}
MyViewModel
public class MyViewModel : INotifyPropertyChanged
{
private Data _data;
public string ID
{
get { return _data.Id;}
set
{
if(_data.Id != value)
{
_data.Id = value;
NotifyPropertyChanged();
}
}
}
// Same stuff for the other properties
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void SearchInDb(string id)
{
// request data to sql server database
// and then populate a Data Class Object with the data retrieved
// from database:
_data = new Data()
{
Id = sReader[0].ToString().Trim(),
Name = sReader[1].ToString().Trim(),
Description = sReader[2].ToString().Trim()
};
NotifyPropertyChanged(nameof(ID));
NotifyPropertyChanged(nameof(Name));
NotifyPropertyChanged(nameof(Description));
}
}
There was nothing wrong with your NotifyPropertyChanged code. It is just an old way of doing it. This way is more modern and does not require magic strings ;-).
You can also bind the Command dependency property of your button to your SearchInDb methods by using a Command property in you view model. This way, you do not need to write code in your code behind. But that's another question :D.
And there is no need for your View to implements INotifyPropertyChanged (unless your case specifically required this).
I have application that works with Model View ViewModel.
In my model I have a List based on my Client class.
public class Client
{
public string Name { get; set; }
public string Ip { get; set; }
public string Mac { get; set; }
}
In my ClientRepository I make a List from XML file with my Client class.
public ClientRepository()
{
var xml = "Clients.xml";
if (File.Exists(xml))
{
_clients = new List<Client>();
XDocument document = XDocument.Load(xml);
foreach (XElement client in document.Root.Nodes())
{
string Name = client.Attribute("Name").Value;
string Ip = client.Element("IP").Value;
string Mac = client.Element("MAC").Value;
_clients.Add(new Client() { Mac = Mac, Name = Name, Ip = Ip });
}
}
}
In my UI/UX I have 3 Textboxes 1 for MAC, 1 IP and 1 Name I also have a Button thats has a binding to AddClientCommand.
<Label Grid.Row="0" Grid.Column="0" Content="Host Name:"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="tbHostName" Height="20" Text="{Binding Path=newClient.Name, UpdateSourceTrigger=PropertyChanged}"/>
<Label Grid.Row="1" Grid.Column="0" Content="IP Address:"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="tbIP" Height="20" Text="{Binding Path=newClient.Ip, UpdateSourceTrigger=PropertyChanged}"/>
<Label Grid.Row="2" Grid.Column="0" Content="MAC Address"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="tbMAC" Height="20" Text="{Binding Path=newClient.Mac, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Row="3" Grid.Column="0" Content="Remove" x:Name="bRemove" Margin="3 0 3 0" Click="bRemove_Click"/>
<Button Grid.Row="3" Grid.Column="1" Content="Add" x:Name="bAdd" Margin="3 0 3 0" Click="bAdd_Click" Command="{Binding AddClientCommand}"/>
To come to my point: What I want to know is what is the best way to implement the AddClientCommand?
What I currently have and I know it doesn't work:
public ClientViewModel()
{
_repository = new ClientRepository();
_clients = _repository.GetClients();
WireCommands();
}
private void WireCommands()
{
AddClientCommand = new RelayCommand(AddClient);
}
public Client newClient
{
get
{
return _newClient;
}
set
{
_newClient = value;
OnPropertyChanged("newClient");
AddClientCommand.isEnabled = true;
}
}
public void AddClient()
{
_repository.AddClient(newClient);
}
RelayCommand class:
public class RelayCommand : ICommand
{
private readonly Action _handler;
private bool _isEnabled;
public RelayCommand(Action handler)
{
_handler = handler;
}
public bool isEnabled
{
get { return true; }
set
{
if (value != isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return isEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
I recommend you to use DelegateCommands, you will find this class in many MVVM frameworks:
public ICommand AddClientCommand
{
get
{
return new DelegateCommand(AddClient, CanExecuteAddClient);
}
}
I also see that _clients is of type List<Client>. If you are binding this to the UI to see the clients list, changes will not be notified unless you use ObservableCollection<Client>
Edit: As someone pointed out in comments, you should create the _newClient. Be aware of creating a new one for each client added, or you will end up adding the same instance of Client over and over!
Have you just tried putting your command into a property something like this?:
public ICommand AddClientCommand
{
get { return new RelayCommand(AddClient, CanAddClient); }
}
public bool CanAddClient()
{
return newClient != null;
}
Put whatever logic you want to inside the CanAddClient to enable or disable the ICommand.
Ahhhh... I see... you have the wrong implementation of the RelayCommand. You need one that uses the CanExecuteChanged event handler... you can find the correct implementation in the RelayCommand.cs page on GitHub.
I have the following listpicker in my XAML;
<toolkit:ListPicker Name="CategoryPicker" ItemsSource="{Binding Category}" CacheMode="BitmapCache" FullModeHeader="{Binding Path=Resources.TheHeader}" SelectedIndex="{Binding TheCurrentIndex, Mode=OneWay}" IsEnabled="{Binding IsViewEnabled}" TabIndex="0" Margin="12,229,12,-205" SelectionChanged="CategoryPicker_SelectionChanged">
<toolkit:ListPicker.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="{Binding CategoryDesc}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" FontSize="{StaticResource PhoneFontSizeMediumLarge}" />
</StackPanel>
</DataTemplate>
</toolkit:ListPicker.ItemTemplate>
<toolkit:ListPicker.FullModeItemTemplate>
<DataTemplate>
<StackPanel x:Name="item" Orientation="Horizontal" Margin="5, 24, 0, 24">
<TextBlock Margin="15, 0, 0, 0" Text="{Binding CategoryDesc}" FontSize="40" TextWrapping="Wrap" />
</StackPanel>
</DataTemplate>
</toolkit:ListPicker.FullModeItemTemplate>
I have been able to populate the listpicker from a SQL CE data table via LINQ, but I am struggling to retrieve the value of the selecteditem.
I have tried the following;
ListPickerItem selectedItem = CategoryPicker.ItemContainerGenerator.ContainerFromItem(this.CategoryPicker.SelectedItem) as ListPickerItem;
I don't think I have understood this properly but I can't seem to to read the text value of the selected item, all help, as always, is appreciated!
edit
The original table definition for categories is below;
[Table(Name = "Categories")]
public class Categories : INotifyPropertyChanged, INotifyPropertyChanging
{
// Define ID: private field, public property and database column.
private int _categoryId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int CategoryId
{
get
{
return _categoryId;
}
set
{
if (_categoryId != value)
{
NotifyPropertyChanging("CategoryId");
_categoryId = value;
NotifyPropertyChanged("CategoryId");
}
}
}
// Define item category: private field, public property and database column.
private string _categoryDesc;
[Column]
public string CategoryDesc
{
get
{
return _categoryDesc;
}
set
{
if (_categoryDesc != value)
{
NotifyPropertyChanging("CategoryDesc");
_categoryDesc = value;
NotifyPropertyChanged("CategoryDesc");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Used to notify the data context that a data context property is about to change
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
The property for Category is;
private ObservableCollection<DBControl.Categories> _category;
public ObservableCollection<DBControl.Categories> Category
{
get
{
return _category;
}
set
{
if (_category != value)
{
_category = value;
NotifyPropertyChanged("Category");
}
}
}
Hope this helps.
It doesn't look like your accessing the SelectedItem the right way. Basically when your working with SQL CE in WP7 and you bind results you recieved from the database to a data control it is storing the object of that specific type.
In this case lets say the item you recieved from the database is of type Category and you bind that to the ListPicker. When someone selected an item, you would simply access it like so:
Categories selectedCategory = CategoryPicker.SelectedItem as Categories;