Apologies in advance I have less than a month of XAML and WPF experience and have been googling as much as possible but here we go.
I have a window that has a single frame inside of it, I have been using pages that swap out what is inside the frame. I have been doing this like this for example:
private void NextPageButtonPressed(object sender, RoutedEventArgs e)
{
this.NavigationService.Navigate(new Page2())
}
This works just fine for navigation when data is not being passed but when I try to go from page2 to page3 and try to pass data from various checkboxes and combo boxes as so:
private void GoToNextPage(object sender, RoutedEventArgs e)
{
object[] isKaliAndDistroSelected = new object[] {GetIsKali(), GetSelectedDistro()};
Page3 pg3 = new Page3();
this.NavigationService.LoadCompleted += pg3.NavigationService_LoadCompleted;
this.NavigationService.Navigate(pg3, isKaliAndDistroSelected);
}
with the following code on Page3:
private string distro="";
private bool isKali = false;
public Page3()
{
InitializeComponet();
RunOnDoneLoadingPage(distro,isKali);
}
public void NavigationService_LoadCompleted(object sender, NavigationEventArgs e)
{
var test = (object[]) e.ExtraData;
isKali = (bool) test[0];
distro = (string) test[1];
NavigationService.LoadCompleted -= NavigationService_LoadCompleted;
}
it ends up not updating the values isKali or distro unless I place the RunOnDoneLoadingPage(distro,isKali); at the end of the NavigationService_LoadCompletedfunction on Page3. The reason I do not want this is because I want the page to load and THEN run the RunOnDoneLoadingPage() because the function manipulates a loading/progress bar. When I place it in the NavigationService_LoadCompleted page2 always freezes for a few before displaying Page3 with a loading/progress at 100% making the loading page essentially useless.
So my question is how can I pass data between 2 pages and have the next page display itself but not start a method until the data from the previous page has been passed
Thank you in advance, like I said I'm fairly new to working with XAML so if I can provide more please let me know
MVVM can help you.
this is a simple view model:
public class MyVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string propAOfPage1;
public string PropAOfPage1
{
get => propAOfPage1;
set
{
if (value != this.propAOfPage1)
{
propAOfPage1= value;
NotifyPropertyChanged();
}
}
}
private string propBOfPage1;
public string PropBOfPage1
{
get => propBOfPage1;
set
{
if (value != this.propBOfPage1)
{
propBOfPage1= value;
NotifyPropertyChanged();
}
}
}
private string propCOfPage2;
public string PropCOfPage2
{
get => propCOfPage2;
set
{
if (value != this.propCOfPage2)
{
propCOfPage2= value;
NotifyPropertyChanged();
}
}
}
private string propDOfPage2;
public string PropDOfPage2
{
get => propDOfPage2;
set
{
if (value != this.propDOfPage2)
{
propDOfPage2= value;
NotifyPropertyChanged();
}
}
}
}
and here is a simple view:
<!-- Page1 -->
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Prop A:" />
<TextBox Text="{Binding PropAOfPage1}" Width="100" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Prop B:" />
<TextBox Text="{Binding PropBOfPage1}" Width="100" />
</StackPanel>
</StackPanel>
<!-- Page2 -->
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Prop C:" />
<TextBox Text="{Binding PropCOfPage2}" Width="100" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Prop D:" />
<TextBox Text="{Binding PropDOfPage2}" Width="100" />
</StackPanel>
</StackPanel>
just set Frame.DataContext as a MyVM object, them this MyVM object will share between your pages. you can navigate freely and need not consider to get or set the control property in each page.
Extended reading: https://www.google.com/search?q=wpf+frame+page+mvvm
Related
So I've been making this program for my end project. and I'm using pages in a tab control to us in my WPF. in these pages, the user fills in all the data in the pages and then press a button, that will save the data and send it to the server. Or they can load in the data from the server, and the data should appear in the textboxes in the MVVM. But it doesn't, neither does it read any data from the textboxes. I can't figure out what I'm doing wrong. Under here are some bits of my code (it has 59 items, so I just show a few). And only the insert from the server. since if this is fixed, the save/update would also be fixed easily I think. Thank you in advance
Window.xaml.cs -
public void DataInlezen(string Json)
{
DataGebruiker viewmodel = new DataGebruiker();
var Djson = JsonConvert.DeserializeObject<JsonCS>(Json);
foreach (var AlgeInf in Djson.algemeneInformatie)
{
viewmodel.Achternaam = AlgeInf.Achternaam;
viewmodel.Voornaam = AlgeInf.Voornaam;
viewmodel.Straat = AlgeInf.Straat;
viewmodel.Huisnummer = AlgeInf.Huisnummer;
viewmodel.Postcode = AlgeInf.Postcode;
viewmodel.Stad = AlgeInf.Stad;
viewmodel.Email = AlgeInf.Email;
viewmodel.GSM = AlgeInf.GSM;
viewmodel.Beroep = AlgeInf.Beroep;
viewmodel.Leeftijd = AlgeInf.Leeftijd;
viewmodel.Geslacht = AlgeInf.Geslacht;
}
Model -
public class DataGebruiker : INotifyPropertyChanged
{
private string _Achternaam;
public string Achternaam
{
get
{
return _Achternaam;
}
set
{
_Achternaam = value;
OnPropertyChanged("Achternaam");
}
}
private string _Voornaam;
public string Voornaam
{
get
{
return _Voornaam;
}
set
{
_Voornaam = value;
OnPropertyChanged("Voornaam");
}
}
//more stuff
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
ViewModel -
internal class DataGebruikerViewModel
{
private readonly DataGebruiker _DataGebruiker;
public DataGebruikerViewModel()
{
_DataGebruiker = new DataGebruiker();
}
public DataGebruiker DataGebruiker
{
get
{
return _DataGebruiker;
}
}
XAML -
<Page.DataContext>
<local:DataGebruikerViewModel/>
</Page.DataContext>
<!--some layout stuff-->
<TextBox Name="TextBoxAchternaam" Grid.Column="1" Grid.Row="0" Text="{Binding DataGebruiker.Achternaam}" FontSize="24" VerticalContentAlignment="Center" Margin="6" />
<TextBox Name="TextBoxVoornaam" Grid.Column="1" Grid.Row="1" Text="{Binding DataGebruiker.Voornaam}" FontSize="24" VerticalContentAlignment="Center" Margin="6" />
<Button Name="ButtonOpzoeken" Grid.Column="1" Content="Opzoeken" Grid.Row="8" Width="150" HorizontalAlignment="Center" FontSize="24" Click="ButtonOpzoeken_Click"/> //button to save
I believe it is because the instance of that DataGebruiker which is binded in the view is different than the one instantiated by DataInlezen method.
I'm making an input page and I'm trying to implement a reset button. After a click on the button, the UI should be empty again.
I thought that entering an empty string would deal with this. In the code it seems to work and the value does get changed to "" but in the UI the typed text stays visible (so it doesn't show the empty "" string). I also tried with string.Empty as suggested in here but that also doesn't seem to work.
Am I missing something here? I'm kinda new to programming so if I did something horribly wrong, don't laugh too hard ;)
I'm using an MVVM pattern and Fody Weaver to deal with the property changed part of the code.
The UI / XAML
<TextBlock Text="Naam:"
Grid.Column="0"
Style="{StaticResource InputInputBlock}"
/>
<TextBox Foreground="White"
Grid.Column="1"
Text="{Binding Name, Mode=TwoWay}"
Style="{StaticResource InputInputBox}"
/>
<Button Content="Reset"
Height="50"
Width="150"
Grid.Column="0"
Grid.Row="2"
VerticalAlignment="Top"
HorizontalAlignment="Center"
Style="{StaticResource FlatButton}"
Command="{Binding ResetCommand}"
/>
The view model
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
}
}
public AddStakeholderViewModel()
{
ResetCommand = new RelayCommand(() => ResetForm());
}
private void ResetForm()
{
Name = " ";
}
You can implement the INotifyPropertyChanged interface in your class. This works for me:
public class Person : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
XAML:
<TextBox Foreground="White"
Grid.Column="1"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Style="{StaticResource InputInputBox}"
/>
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = newPerson;
}
Person newPerson = new Person();
private void button_Click(object sender, RoutedEventArgs e)
{
newPerson.Name = "";
}
}
I am creating a to do list application. At the moment I want to add a new to do list from todolistPage.xaml and after adding, I want to take the data to be able to view in the MainPage.xaml . I am able to view it from the todolistPage but not sure how to bring it to to another page. Hope to have some help. Thanks.
Below are my codes
MainPage.xaml.cs
namespace PivotApp3
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// Set the data context of the listbox control to the sample data
DataContext = App.ViewModel;
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
// Load data for the ViewModel Items
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
private void LongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var si = mLongListSelector.SelectedItem as PivotApp3.ViewModels.ItemViewModel;
if (mLongListSelector.SelectedItem == null)
return;
if (si.LineOne.Equals("+ To Do List"))
NavigationService.Navigate(new Uri("/todolistPage.xaml", UriKind.Relative));
else if (si.LineOne.Equals("+ Reminder"))
NavigationService.Navigate(new Uri("/reminderPage.xaml", UriKind.Relative));
// Reset selected item to null (no selection)
mLongListSelector.SelectedItem = null;
}
}
MainPage.xaml
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<!-- LOCALIZATION NOTE:
To localize the displayed strings copy their values to appropriately named
keys in the app's neutral language resource file (AppResources.resx) then
replace the hard-coded text value between the attributes' quotation marks
with the binding clause whose path points to that string name.
For example:
Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
This binding points to the template's string resource named "ApplicationTitle".
Adding supported languages in the Project Properties tab will create a
new resx file per language that can carry the translated values of your
UI strings. The binding in these examples will cause the value of the
attributes to be drawn from the .resx file that matches the
CurrentUICulture of the app at run time.
-->
<!--Pivot Control-->
<phone:Pivot Title="DAILY ROUTINE">
<!--Pivot item one-->
<phone:PivotItem Header="activity">
<!--Double line list with text wrapping-->
<phone:LongListSelector x:Name="mLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="LongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</phone:PivotItem>
<!--Pivot item two-->
<phone:PivotItem Header="today">
</phone:PivotItem>
</phone:Pivot>
<!--Uncomment to see an alignment grid to help ensure your controls are
aligned on common boundaries. The image has a top margin of -32px to
account for the System Tray. Set this to 0 (or remove the margin altogether)
if the System Tray is hidden.
Before shipping remove this XAML and the image itself.-->
<!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" IsHitTestVisible="False" />-->
</Grid>
todolistPage.xaml.cs
namespace PivotApp3
{
public partial class todolistPage : PhoneApplicationPage, INotifyPropertyChanged
{
// Data context for the local database
private ToDoDataContext toDoDB;
// Define an observable collection property that controls can bind to.
private ObservableCollection<ToDoItem> _toDoItems;
public ObservableCollection<ToDoItem> ToDoItems
{
get
{
return _toDoItems;
}
set
{
if (_toDoItems != value)
{
_toDoItems = value;
NotifyPropertyChanged("ToDoItems");
}
}
}
//constructor
public todolistPage()
{
InitializeComponent();
// Connect to the database and instantiate data context.
toDoDB = new ToDoDataContext(ToDoDataContext.DBConnectionString);
// Data context and observable collection are children of the main page.
this.DataContext = this;
}
private void deleteTaskButton_Click(object sender, RoutedEventArgs e)
{
// Cast parameter as a button.
var button = sender as Button;
if (button != null)
{
// Get a handle for the to-do item bound to the button.
ToDoItem toDoForDelete = button.DataContext as ToDoItem;
// Remove the to-do item from the observable collection.
ToDoItems.Remove(toDoForDelete);
// Remove the to-do item from the local database.
toDoDB.ToDoItems.DeleteOnSubmit(toDoForDelete);
// Save changes to the database.
toDoDB.SubmitChanges();
// Put the focus back to the main page.
this.Focus();
}
}
private void newToDoTextBox_GotFocus(object sender, RoutedEventArgs e)
{
// Clear the text box when it gets focus.
newToDoTextBox.Text = String.Empty;
}
private void newToDoAddButton_Click(object sender, RoutedEventArgs e)
{
// Create a new to-do item based on the text box.
ToDoItem newToDo = new ToDoItem { ItemName = newToDoTextBox.Text };
// Add a to-do item to the observable collection.
ToDoItems.Add(newToDo);
// Add a to-do item to the local database.
toDoDB.ToDoItems.InsertOnSubmit(newToDo);
}
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
// Call the base method.
base.OnNavigatedFrom(e);
// Save changes to the database.
toDoDB.SubmitChanges();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Define the query to gather all of the to-do items.
var toDoItemsInDB = from ToDoItem todo in toDoDB.ToDoItems
select todo;
// Execute the query and place the results into a collection.
ToDoItems = new ObservableCollection<ToDoItem>(toDoItemsInDB);
// Call the base method.
base.OnNavigatedTo(e);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the app that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public class ToDoDataContext : DataContext
{
// Specify the connection string as a static, used in main page and app.xaml.
public static string DBConnectionString = "Data Source=isostore:/ToDo.sdf";
// Pass the connection string to the base class.
public ToDoDataContext(string connectionString)
: base(connectionString)
{ }
// Specify a single table for the to-do items.
public Table<ToDoItem> ToDoItems;
}
[Table]
public class ToDoItem : INotifyPropertyChanged, INotifyPropertyChanging
{
// Define ID: private field, public property and database column.
private int _toDoItemId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int ToDoItemId
{
get
{
return _toDoItemId;
}
set
{
if (_toDoItemId != value)
{
NotifyPropertyChanging("ToDoItemId");
_toDoItemId = value;
NotifyPropertyChanged("ToDoItemId");
}
}
}
// Define item name: private field, public property and database column.
private string _itemName;
[Column]
public string ItemName
{
get
{
return _itemName;
}
set
{
if (_itemName != value)
{
NotifyPropertyChanging("ItemName");
_itemName = value;
NotifyPropertyChanged("ItemName");
}
}
}
// Define completion value: private field, public property and database column.
private bool _isComplete;
[Column]
public bool IsComplete
{
get
{
return _isComplete;
}
set
{
if (_isComplete != value)
{
NotifyPropertyChanging("IsComplete");
_isComplete = value;
NotifyPropertyChanged("IsComplete");
}
}
}
// Version column aids update performance.
[Column(IsVersion = true)]
private Binary _version;
#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
}
todolistPage.xaml
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="TO DO LIST" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="add" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!-- Bind the list box to the observable collection. -->
<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding ToDoItems}"
Grid.Row="1" Margin="12,0,28,210" Width="440">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<CheckBox
IsChecked="{Binding IsComplete, Mode=TwoWay}"
Grid.Column="0"
VerticalAlignment="Center"/>
<TextBlock
Text="{Binding ItemName}"
FontSize="{StaticResource PhoneFontSizeLarge}"
Grid.Column="1"
VerticalAlignment="Center"/>
<Button
Grid.Column="2"
x:Name="deleteTaskButton"
BorderThickness="0"
Margin="0"
Click="deleteTaskButton_Click">
<Image Source="appbar.delete.rest.png"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="2" Margin="12,465,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<TextBox
x:Name="newToDoTextBox"
Grid.Column="0"
Text="add new task"
FontFamily="{StaticResource PhoneFontFamilyLight}"
GotFocus="newToDoTextBox_GotFocus" Margin="0,-65,0,104" Grid.ColumnSpan="2"/>
<Button
Content="add"
x:Name="newToDoAddButton"
Click="newToDoAddButton_Click" Margin="150,43,130,10" Grid.ColumnSpan="2"/>
</Grid>
</Grid>
Database Created
using (ToDoDataContext db = new ToDoDataContext(ToDoDataContext.DBConnectionString))
{
if (db.DatabaseExists() == false)
{
//Create the database
db.CreateDatabase();
}
}
Put your database into App.xaml.cs:
// Data context for the local database
public ToDoDataContext toDoDB;
add this code to App.xaml.cs:
public new static App Current
{
get
{
return (App)Application.Current;
}
}
Then you can access your database everywhere by using:
App.Current.toDoDB...
You can create class to manipulate data in sql and make it avaiable from App.xaml.cs
Here is sample code
ToDoDataViewModel class:
public class ToDoDataViewModel : INotifyPropertyChanged
{
ToDoDataContext db;
public ToDoDataViewModel(string connectionString)
{
db = new ToDoDataContext(connectionString);
}
private ObservableCollection<ToDoItem> _toDoItems;
public ObservableCollection<ToDoItem> ToDoItems
{
get { return this._toDoItems; }
set
{
this._toDoItems = value;
NotifyPropertyChanged("ToDoItems");
}
}
public void LoadCollectionsFromDatabase()
{
var toDos = from todo in db.ToDoItems
select todo;
_toDoItems = new ObservableCollection<ToDoItem>(toDos);
}
public void InsertToDoItem(ToDoItem item)
{
db.ToDoItems.InsertOnSubmit(item);
_toDoItems.Add(item);
db.SubmitChanges();
}
public void DeleteToDoItem(ToDoItem item)
{
db.ToDoItems.DeleteOnSubmit(item);
_toDoItems.Remove(item);
db.SubmitChanges();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In App.xaml.cs:
public partial class App : Application
{
private static ToDoDataViewModel _viewModel;
public static ToDoDataViewModel ViewModel
{
get { return _viewModel; }
}
//other methods of App
public App()
{
//place this code at the and of the contructor
CreateDb();
}
private void CreateDb()
{
using(var db=new ToDoDataContext(ToDoDataContext.DBConnectionString))
{
if(!db.DatabaseExists())
{
db.CreateDatabase();
}
}
_viewModel=new ToDoDataViewModel(ToDoDataContext.DBConnectionString);
_viewModel.LoadCollectionsFromDatabase();
}
}
And place this code in the constructors of your pages:
this.DataContext=App.ViewModel;
This way you separated your database logic from application logic
Now you can update your newToDoAddButton_Click method as following:
private void newToDoAddButton_Click(object sender, RoutedEventArgs e)
{
// Create a new to-do item based on the text box.
ToDoItem newToDo = new ToDoItem { ItemName = newToDoTextBox.Text };
//Add to-do item to the local database
App.ViewModel.InsertToDoItem(newToDo);
this.Focus();
}
And your deleteTaskButton_Click method:
private void deleteTaskButton_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
// Get a handle for the to-do item bound to the button.
ToDoItem toDoForDelete = button.DataContext as ToDoItem;
// Remove the to-do item from the local database.
App.ViewModel.DeleteToDoItem(toDoForDelete);
this.Focus();
}
}
I have a MVVM setup that creates a View on my MainWindow. I am not sure how to know when a user Clicks on a specific Notification Item inside the View. Where would I add the event, or a command to know when that happens?
here are is my MVVM code :
MainWindow
cs:
NotificationViewModel notificationViewModel = new NotificationViewModel();
notificationViewModel.AddNoticiation(new NotificationModel() { Message = "Error", Name = "Station 21" });
NotificationView.DataContext = notificationViewModel;
xaml:
<notification:NotificationView x:Name="NotificationView" />
NotificationModel
public class NotificationModel : INotifyPropertyChanged
{
private string _Message;
public string Message
{
get { return _Message; }
set
{
if (_Message != value)
{
_Message = value;
RaisePropertyChanged("Message");
}
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
RaisePropertyChanged("Name");
}
}
}
public string TimeStamp
{
get { return DateTime.Now.ToString("h:mm:ss"); }
}
#region PropertChanged Block
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
}
NotificationViewModel
public class NotificationViewModel
{
private ObservableCollection<NotificationModel> _Notifications = new ObservableCollection<NotificationModel>();
public ObservableCollection<NotificationModel> Notifications
{
get { return _Notifications; }
set { _Notifications = value; }
}
public void AddNoticiation(NotificationModel notification)
{
this.Notifications.Insert(0, notification);
}
}
NotificationView
<Grid>
<StackPanel HorizontalAlignment="Left" >
<ItemsControl ItemsSource="{Binding Path=Notifications}"
Padding="5,5,5,5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="SlateGray"
CornerRadius="4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Path=TimeStamp}" />
<TextBlock Grid.Column="1"
Text="{Binding Path=Name}" />
<TextBlock Grid.Column="2"
Text="{Binding Path=Message}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
There's no real selection mechanism built into an ItemsControl. It would probably solve your problem to switch out your ItemsControl for a ListBox.
If you do that, you can bind to SelectedItem, then handle any changes made to SelectedItem using the PropertyChanged event.
Example:
In your view model's constructor:
PropertyChanged += NotificationViewModel_PropertyChanged;
Add a property to your view model to allow the binding:
private string _selectedNotification;
public string SelectedNotification
{
get { return _selectedNotification; }
set
{
if (_selectedNotification != value)
{
_selectedNotification = value;
RaisePropertyChanged("SelectedNotification");
}
}
}
Finally, add the event handler to your view model:
NotificationViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e))
{
if (e.PropertyName = "SelectedNotification") DoStuff();
}
You may find that you don't even need to hook into PropertyChanged if you just want to update another control in your view based on the selected item in your list box. You can just bind directly to the property within xaml.
I have MainWindow containing a datagrid and a "filter panel". The filter panel can change by a user input(button click). I try to achieve it with databinding. The problem that Im facing is the filter panel(which is a user control) is not loaded or refreshed.
Mainwindow xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250*" />
<ColumnDefinition Width="253*" />
</Grid.ColumnDefinitions>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Margin="23,28,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="200" ItemsSource="{Binding OverviewableItems}" />
<UserControl Grid.Column="1" Content="{Binding UserControl}" DataContext="{Binding}" Grid.ColumnSpan="2" />
<Button Content="PersonFilter" Height="23" HorizontalAlignment="Left" Margin="23,268,0,0" Name="buttonPersonFilter" VerticalAlignment="Top" Width="75" Click="buttonPersonFilter_Click" />
<Button Content="ProjectFilter" Height="23" HorizontalAlignment="Left" Margin="132,268,0,0" Name="buttonProjectFilter" VerticalAlignment="Top" Width="75" Click="buttonProjectFilter_Click" />
</Grid>
code behind:
private ViewModel _viewModel;
public MainWindow()
{
_viewModel = new ViewModel(new DataProvider());
DataContext = _viewModel;
_viewModel.PropertyChanged += _viewModel.SetFilterType;
InitializeComponent();
}
private void buttonProjectFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Project;
}
private void buttonPersonFilter_Click(object sender, RoutedEventArgs e)
{
_viewModel.OverviewType = OverviewType.Person;
}
First user control:
<Grid>
<DatePicker Grid.Column="1" Grid.Row="1" Height="25" HorizontalAlignment="Left" Margin="19,18,0,0" Name="datePickerFundingTo" VerticalAlignment="Top" Width="115" Text="{Binding ElementName=ProjectFilter, Path=FundingTo}" />
</Grid>
code behind for this user control is only this:
public DateTime FundingTo { get; set; }
public ProjectFilter()
{
FundingTo = DateTime.Now;
InitializeComponent();
}
Other user control: just simply contains a TextBox and a Button, for the sake of simplicity I didnt add any code behind to it.
ViewModel of the MainWindow:
public class ViewModel : INotifyPropertyChanged
{
private UserControl _userControl;
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl == value)
{
return;
}
OnPropertyChanged("UserControl");
_userControl = value;
}
}
private OverviewType _overviewType = OverviewType.None;
public OverviewType OverviewType
{
get { return _overviewType; }
set
{
if (_overviewType == value)
{
return;
}
OnPropertyChanged("OverviewType");
_overviewType = value;
}
}
private ObservableCollection<IOverviewItem> _overviewableItems;
public ObservableCollection<IOverviewItem> OverviewableItems
{
get { return _overviewableItems; }
set
{
if (_overviewableItems == value)
{
return;
}
_overviewableItems = value;
}
}
private readonly DataProvider _dataProvider;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel(DataProvider dataProvider)
{
_dataProvider = dataProvider;
}
public void SetFilterType(object sender, EventArgs eventArgs)
{
switch (_overviewType)
{
case OverviewType.Project:
_userControl = new ProjectFilter();
break;
case OverviewType.Person:
_userControl = new PersonFilter();
break;
}
}
public void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
var eventArgs = new PropertyChangedEventArgs(name);
PropertyChanged(this, eventArgs);
}
}
plus I have an enum OverviewType with None,Project,Person values.
The property changed event fired properly, but the user control is not refreshed. Could anyone enlight me, where is the flaw in my solution?
And the other question I have, how can I communicate from the usercontrols to the mainwindow viewmodel? Forex: the datagrid should be changed according to its filter.
Any help would be greatly appreciated. Cheers!
There are different problems here.
As Clemens said, you must fire your event after the value is updated. But it's not the main issue here.
Second problem: you are affecting your new usercontrol to the private member, so you're totally bypassing your property.
Replace
_userControl = new ProjectFilter();
by
this.UserControl = new ProjectFilter();
Third problem, which is not directly related to your question but actually is your biggest problem: you have an architecture design issue. You're exposing in your viewmodel a UserControl, which is an anti-pattern. Your viewmodel must not know anything about the view, so it must NOT have any reference to the controls inside the view. Instead of the binding you wrote, you could fire an event from the viewmodel and add an event handler in your view so it's your view that updates the usercontrol.
Try to fire the PropertyChanged after changing a property's backing field:
public UserControl UserControl
{
get { return _userControl; }
set
{
if (_userControl != value)
{
_userControl = value; // first
OnPropertyChanged("UserControl"); // second
}
}
}
Similar for OverviewType.