Xamaring Forms Page Load Order Changes - c#

I have a Xamarin form where I have a list and two buttons. What I am seeing is that, depending where the buttons are, the model loads differently. Here is my Xaml code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.RewardsPage"
Title="{Binding Title}"
xmlns:local="clr-namespace:MyApp.ViewModels"
xmlns:model="clr-namespace:MyApp.Models" x:DataType="local:RewardsViewModel"
x:Name="BrowseItemsPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="650" />
</Grid.RowDefinitions>
<CollectionView x:Name="ItemsListView"
ItemsSource="{Binding Items}"
SelectionMode="None" Grid.Row="1" >
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:RewardModel">
<Label Text="{Binding id, StringFormat='ID: {0}'}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<!--other labels removed for brevity-->
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.Footer>
<StackLayout Orientation="Horizontal">
<Button Text="Previous Month" Command="{Binding PreviousMonthCommand}" HorizontalOptions="FillAndExpand"></Button>
<Button Text="Next Month" Command="{Binding NextMonthCommand}" HorizontalOptions="FillAndExpand"></Button>
</StackLayout>
</CollectionView.Footer>
</CollectionView>
</Grid>
</ContentPage>
The code works fine here. But, if I move the StackLayout from the CollectionView.Footer to its own grid row, like this:
</CollectionView>
<!--other labels removed for brevity-->
</CollectionView>
<StackLayout Orientation="Horizontal" Grid.Row="2">
<Button Text="Previous Month" Command="{Binding PreviousMonthCommand}" HorizontalOptions="FillAndExpand"></Button>
<Button Text="Next Month" Command="{Binding NextMonthCommand}" HorizontalOptions="FillAndExpand"></Button>
</StackLayout>
Then the code in my RewardsViewModel executes in a different order. Here is my RewardsViewModel code (simplified):
[QueryProperty(nameof(CurrentMonth), nameof(CurrentMonth))]
[QueryProperty(nameof(CurrentYear), nameof(CurrentYear))]
public class RewardsViewModel: BaseViewModel
{
public ObservableCollection<RewardModel> Items { get; }
private List<MonthModel> months;
private int _current_year;
private int _current_month;
public Command PreviousMonthCommand { get; }
public Command NextMonthCommand { get; }
public RewardsViewModel()
{
Items = new ObservableCollection<RewardModel>();
PreviousMonthCommand = new Command(OnPrevious, ValidatePrevious);
NextMonthCommand = new Command(OnNext, ValidateNext);
}
public int CurrentYear
{
get
{
return _current_year ;
}
set
{
_current_year = value;
LoadItems();
}
}
public int CurrentMonth
{
get
{
return _current_month;
}
set
{
_current_month= value;
LoadItems();
}
}
public void LoadItems()
{
IsBusy = true;
//do stuff
}
private bool ValidatePrevious()
{
//do stuff to validate and return true or false
}
private bool ValidateNext()
{
//do stuff to validate and return true or false
}
private void OnPrevious()
{
//do stuyff
}
private void OnNext()
{
//do stuff
}
}
Depending on where the buttons reside in the Xaml page, the load events change:
When the buttons are within CollectionView, and the page loads, first the constructor loads then the Query Parameter setters load (CurrentMonth set then CurrentYear set)
When the buttons are outside the CollectionView, first the constructor loads then ValidatePrevious method is called and then ValidateNext method is called.
Why does the placement of the buttons in the Xaml file change the order of operations in my ViewModel? And, how do I ensure that the Query Parameter setters are called first, regardless of where the buttons reside?
Edit:
This is the code, from the previous page, that loads this page, passing in the Query Parameters:
async void OnItemSelected(MonthModel item)
{
if (item == null)
return;
await Shell.Current.GoToAsync($"{nameof(RewardsPage)}?{nameof(RewardsViewModel.CurrentYear)}={CurrentYear}&CurrentMonth={SelectedItem.month}");
}
Edit: Adding Base Class:
public class BaseViewModel : INotifyPropertyChanged
{
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}

Related

Progress Bar Not Updating Xamarin Forms MVVM

I know this has been asked before but I've spent ages and nothing has helped.
I'm trying to update a progress bar from a ViewModel however it will not update.
Recipe.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="FitnessScript.Views.Recipes">
<ContentPage.Content>
<StackLayout>
<Label Text="Please Enter Ingredients and Requirements!"
HorizontalOptions="Center"
VerticalOptions="Start" HorizontalTextAlignment="Center" TextType="Text"
Margin="0,20,0,0"
FontSize="25"/>
<Label Text="Enter Ingredients" Margin="5"/>
<Entry x:Name="Ingredients"
Text="{Binding Ingredients}"
Placeholder="Ingredients"
PlaceholderColor="LightGray" />
<Label Text="Enter Calories" Margin="5"/>
<Entry x:Name="Calories"
Text="{Binding Calories}"
Placeholder="Calories"
PlaceholderColor="LightGray" />
<Button x:Name="RecipeSearchBtn"
Text="Find Recipes"
Command="{Binding RequestRecipeCommand}" />
<ProgressBar x:Name="ProgressB"
Progress="{Binding ProgressValue}"
ProgressColor="Purple"
IsVisible="True"/>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Recipes.xmal.cs
namespace FitnessScript.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Recipes : ContentPage
{
RecipeSearchViewModel recipeSearchViewModel;
public Recipes()
{
recipeSearchViewModel = new RecipeSearchViewModel();
InitializeComponent();
BindingContext = recipeSearchViewModel;
}
}
}
RecipeSearchViewModel
namespace FitnessScript.ViewModels
{
public class RecipeSearchViewModel : BaseViewModel
{
private static readonly IRecipeService _recipeService = new RecipeService();
private readonly BackgroundWorker worker;
#region Getters/Setters
string _ingredients;
public string Ingredients
{
get { return _ingredients; }
set
{
_ingredients = value;
OnPropertyChanged("Ingredients");
}
}
int _calories;
public int Calories
{
get { return _calories; }
set
{
_calories = value;
OnPropertyChanged("Calories");
}
}
float _progressValue;
public float ProgressValue
{
get { return _progressValue; }
set
{
_progressValue = value;
OnPropertyChanged("ProgressValue");
}
}
#endregion
public RecipeSearchViewModel()
{
this.worker = new BackgroundWorker();
}
public Command RequestRecipeCommand
{
get
{
return new Command(async () => await RequestRecipe());
}
}
private async Task RequestRecipe()
{
await Task.Run(() =>
{
Device.BeginInvokeOnMainThread(() =>
{ ProgressValue = 1; }
);
});
List<string> ingredientsList = await _recipeService.GetRecipe(Ingredients, Calories);
App.Current.MainPage.DisplayAlert("Success", $"{Ingredients}, {Calories}", "Close");
}
}
}
I Have tired many different alternatives, such as setting ProgressValue to Double and Decimal, forcing the UI thread, with and without adding a parameter to OnPropertyChange(). I've attempted background works too, just nothing sadly.
I'm debugging using a S10+ via USB as I prefer it to emulation.
The overall aim is to press the RecipeSearchBtn, do the logic, and update the progress bar along with it, however for debugging purposes I just want to change the progress to 100% when the button command executes
Any help would be appreaciated, thanks
Also I have tried the Activity Indicator however similar issues, it never showed while debugging though my phone when setting the visibility ect to true through binding IsBool
About binding ActivityIndicator isvisible, I do one sample that you can take a look:
Please take a look the following code, ActivityIndicator display firstly, clicking button to load data, setting ActivityIndicator isVisible and IsRunning as false.
<StackLayout>
<Button
x:Name="btn1"
Command="{Binding command1}"
Text="load data" />
<ActivityIndicator
HeightRequest="50"
IsRunning="{Binding isvisible}"
IsVisible="{Binding isvisible}"
WidthRequest="50"
Color="Red" />
<ListView ItemsSource="{Binding students}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding name}" />
<Label Text="{Binding age}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public partial class Page2 : ContentPage
{
public Page2()
{
InitializeComponent();
this.BindingContext = new studentviewmodel();
}
}
public class studentviewmodel:ViewModelBase
{
public ObservableCollection<studentmodel> students { get; set; }
public Command command1 { get; set; }
private bool _isvisible;
public bool isvisible
{
get { return _isvisible; }
set
{
_isvisible = value;
RaisePropertyChanged("isvisible");
}
}
public studentviewmodel()
{
command1 = new Command(loaddata);
isvisible = true;
students = new ObservableCollection<studentmodel>();
}
private async void loaddata()
{
//call service to do other something.
await Task.Delay(5000);
students.Add(new studentmodel() { name = "cherry", age = 29 });
students.Add(new studentmodel() { name = "barry", age = 30 });
students.Add(new studentmodel() { name = "annine", age = 15 });
isvisible = false;
}
}
public class studentmodel
{
public string name { get; set; }
public int age { get; set; }
}
The ViewModelBase is the class that implementing INotifyPropertyChanged, to notify data changed.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The screenshot:

Xamarin Forms not updating ListView

my PropertyChanged event is not updatin my listview in xamarin.
Could someone help me with that?
The Method RefreshListView is triggered when the searchbar text has changed.
My Viewmodel:
public class LebensmittelViewModel : INotifyPropertyChanged
{
private ObservableCollection<Lebensmittel> lebensmittelList = new ObservableCollection<Lebensmittel>();
public List<Lebensmittel> normalLebensmittelList = new List<Lebensmittel>();
public event PropertyChangedEventHandler PropertyChanged;
public LebensmittelViewModel()
{
normalLebensmittelList = App.LebensmittelDatabase.getAllLebensmittel();
}
public void RefreshListView(string searchBarText)
{
LebensmittelList = addItemInCollection(searchBarText);
}
public ObservableCollection<Lebensmittel> addItemInCollection(string searchBarText)
{
if (searchBarText != null)
{
foreach (var item in normalLebensmittelList)
{
if (item.Name.Contains(searchBarText) || item.Name.Contains(searchBarText.First().ToString().ToUpper()))
{
LebensmittelList.Add(item);
};
}
}
return LebensmittelList;
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<Lebensmittel> LebensmittelList
{
get
{
return lebensmittelList;
}
set
{
lebensmittelList = value;
OnPropertyChanged("LebensmittelList");
}
}
}
Einkaufsliste.xaml
<ContentPage.Content>
<StackLayout Spacing="10" Padding="10">
<SearchBar x:Name="searchBar" Text="{Binding searchBarText}" Placeholder="Lebensmittel suchen..." VerticalOptions="StartAndExpand">
<SearchBar.Behaviors>
<behavior:TextChangedBehavior/>
</SearchBar.Behaviors>
</SearchBar>
<ListView x:Name="listView" ItemsSource="{Binding LebensmittelList}" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout VerticalOptions="FillAndExpand" Orientation="Horizontal" Padding="10">
<Label Text="{Binding Name}" YAlign="Center" Font="Large"/>
<ia:Checkbox HorizontalOptions="EndAndExpand"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
Einkaufsliste.xaml.cs
public Einkaufsliste ()
{
InitializeComponent ();
BindingContext = new LebensmittelViewModel();
}
I think everything should be okay but it's not working.
Hope someone could help me with this,
thanks
Edit for Tom:
public void RefreshListView(string searchBarText)
{
addItemInCollection(searchBarText);
}
public void addItemInCollection(string searchBarText)
{
if (searchBarText != null)
{
foreach (var item in normalLebensmittelList)
{
if (item.Name.Contains(searchBarText) || item.Name.Contains(searchBarText.First().ToString().ToUpper()))
{
AddItemToList(item);
};
}
}
}
private void AddItemToList(Lebensmittel item)
{
lebensmittelList.Add(item);
LebensmittelList = lebensmittelList;
}
How it's implemented the behavior TextChangedBehavior? Do you cast the BindingContext as a LebensmittelViewModel in order to call RefreshListView?
I would get rid of the behavior :/ and add a TextChanged event, create the property SearchBarText, since I don't see where it's declared and you're binding it to the SearchBar, refactor the RefreshListView method... and I think that it should work... :)
Einkaufsliste.xaml.cs
private LebensmittelViewModel vm = new LebensmittelViewModel();
public Einkaufsliste ()
{
InitializeComponent ();
BindingContext = vm;
}
public void OnSeachBarTextChange(object e, TextChangedEventArgs args)
{
vm.RefreshListView();
}
Einkaufsliste.xaml
<ContentPage.Content>
<StackLayout Spacing="10" Padding="10">
<SearchBar x:Name="searchBar" Text="{Binding SearchBarText, Mode=TwoWay}" Placeholder="Lebensmittel suchen..." VerticalOptions="StartAndExpand" TextChanged="OnSeachBarTextChange">
</SearchBar>
<ListView x:Name="listView" ItemsSource="{Binding LebensmittelList}" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout VerticalOptions="FillAndExpand" Orientation="Horizontal" Padding="10">
<Label Text="{Binding Name}" YAlign="Center" Font="Large"/>
<ia:Checkbox HorizontalOptions="EndAndExpand"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
Viewmodel
private string _searchBarText;
public string SearchBarText
{
get
{
return _searchBarText;
}
set
{
_searchBarText = value;
OnPropertyChanged("SearchBarText");
}
}
public void RefreshListView()
{
if (!string.IsNullOrEmpty(searchBarText))
{
var matches = normalLebensmittelList.Where(x => x.Name.Contains(searchBarText) || x.Name.Contains(searchBarText.First().ToString().ToUpper())
foreach (var item in matches)
{
LebensmittelList.Add(item);
}
}
}
Can you try replacing the Following method:
public void RefreshListView(string searchBarText)
{
LebensmittelList.Clear();
addItemInCollection(searchBarText);
}
Since you are already using the Observable Collection you dont need to replace the collection. You can add and it will automatically observed and binded. Let me know if it works!
It looks like you have extra work with LebensmittelList. Try do the following:
public void RefreshListView(string searchBarText)
{
// this can be list clearing, if you need it
addItemInCollection(searchBarText);
OnPropertyChanged("LebensmittelList");
}
public void addItemInCollection(string searchBarText)
{
if (searchBarText != null)
{
foreach (var item in normalLebensmittelList)
{
if (item.Name.Contains(searchBarText) || item.Name.Contains(searchBarText.First().ToString().ToUpper()))
{
LebensmittelList.Add(item);
};
}
}
}
Edit:
I don't know if anyone can call this solution elegant, but it should work.
internal class MyCollection<T> : ObservableCollection<T>
{
public void DoCollectionChanged()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
... MyCollection<LebensmittelItem> LebensmittelList; // field and property declaration should be changed
public void RefreshListView(string searchBarText)
{
// this can be list clearing, if you need it
addItemInCollection(searchBarText);
LebensmittelList.DoCollectionChanged();
}
Is OnPropertyChanged in the property set called when you do the RefreshListView?
In the addItemToCollection method, you are calling the following line:
LebensmittelList.Add(item);
In this case LebensmittelList is a property, not a variable. The line LebensmittelList.Add(item); is effectively doing the following:
var temporaryList = LebensmittelList; // Gets lebensmittelList and assigns to temporaryList
temporaryList.Add(item); // You add the item to the *variable temporaryList*
Essentially, you are adding to an instance of lebensmittelList (a temporary copy), and not the lebensmittelList variable. Once you have added that item to the temporary list, the whole (temporary) list gets discarded without "saving changes".
To actually update LebensmittelList, you would be better off calling a method like:
private void AddItemToList(object item)
{
lebensmittelList.Add(item);
LebensmittelList = lebensmittelList;
}

When i switch to another source in ListView it does'nt update

I'm fairy new to Xamarin, and I trying to do a switching List. So when button is pressed, it switches to another list. I have CustomLists class which wraps all this lists and exposes ChosenList property, which gives access to list currenly being displayed. When entry in List is deleted Command property gets called
public ICommand DeleteTest
{
get { return new Command<TaskRecord>((s) => OnDelete(s)); }
}
void OnDelete(TaskRecord task)
{
List.Remove(task);
IsUnfinishedChanged_ = !task.IsFinished;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChosenList"));
System.Diagnostics.Debug.Print("Deleting..");
}
When I delete entry in a first list (those shown at the programm start) it works fine. But in a second, ListView doesn't update for some reason
Here's my XAML code
<ListView ItemsSource="{Binding ChosenList}" IsPullToRefreshEnabled="True"
Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" SeparatorColor="DimGray">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.ContextActions>
<MenuItem Text="Delete" IsDestructive="true"
Command="{Binding Source={x:Reference MainGrid},
Path=BindingContext.DeleteTest}"
CommandParameter="{Binding .}"/>
</ViewCell.ContextActions>
<StackLayout Padding="15,0" VerticalOptions="Center">
<Label Text="{Binding Path=Name}" FontSize="Large" Font="Arial"
FontAttributes="Bold" VerticalOptions="Center"/>
<Label Text="{Binding Path=ShortDescr}" FontSize="Micro" Font="Arial"
VerticalOptions="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Edit: ChosenList is wrapper for ListView to bind to
public List<TaskRecord> ChosenList
{
get
{
return (IsAll_ ? List : Unfinished);
}
}
Edit 2: Putting the whole code up here
private List<TaskRecord> List { get; set; }
private List<TaskRecord> UnfinishedCache_;
private List<TaskRecord> Unfinished
{
get
{
if (IsUnfinishedChanged_)
{
UnfinishedCache_ = new List<TaskRecord>();
foreach (TaskRecord task in List)
{
if (!task.IsFinished) UnfinishedCache_.Add(task);
}
IsUnfinishedChanged_ = false;
}
return UnfinishedCache_;
}
set { UnfinishedCache_ = value; }
}
private bool IsUnfinishedChanged_=true;
private bool IsAll_;
public ICommand ListChangeCommand
{
get { return new Command<string>((s)=>OnListSwitch(s)); }
}
void OnListSwitch(string senderText)
{
if (senderText == "All" && !IsAll_)
{
IsAll_ = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChosenList"));
}
else if (senderText == "Unfinished" && IsAll_)
{
IsAll_ = false;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChosenList"));
}
}
public ICommand DeleteTest
{
get { return new Command<TaskRecord>((s) => OnDelete(s)); }
}
void OnDelete(TaskRecord task)
{
List.Remove(task);
IsUnfinishedChanged_ = !task.IsFinished;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ChosenList"));
System.Diagnostics.Debug.Print("Deleting..");
}
public event PropertyChangedEventHandler PropertyChanged;
public List<TaskRecord> ChosenList
{
get
{
return (IsAll_ ? List : Unfinished);
}
}

Wpf MVVM - Tabbed interface is not working as expected

First: I am new to MVVM and WPF.
I am trying to create a little application with a tabbed user interface. Users can create products and storage locations, using a button which should open a new TabItem.
My code in the view looks like this:
<TabControl ItemsSource="{Binding Workspaces}"
IsSynchronizedWithCurrentItem="True"
Margin="3"
DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
and the View Model is this:
ObservableCollection<WorkspaceViewModel> _workspaces;
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
}
return _workspaces;
}
set
{
_workspaces = value;
}
}
public void AddProduct(object obj)
{
Workspaces.Add(new ProductViewModel());
}
Various other buttons add different ViewModels to the Workspaces Collection.
I have defined multiple Data Template (one for each ViewModel). Here is one:
<DataTemplate DataType="{x:Type vm:ProductViewModel}">
<vw:ProductView />
</DataTemplate>
The WorkspaceViewModel is this:
namespace Inventory.Desktop.ViewModels
{
public abstract class WorkspaceViewModel : INotifyPropertyChanged
{
#region Events and EventHandlers
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
and eg the ProductViewModel
namespace Inventory.Desktop.ViewModels
{
public class ProductViewModel: WorkspaceViewModel
{
private Product _product;
private string _displayName;
public string DisplayName
{
get
{
if (String.IsNullOrEmpty(_displayName))
{
return "Neues Produkt";
} else
{
return _displayName;
}
}
set
{
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
#region Public Properties
public Product Product
{
get
{
return _product;
}
set
{
_product = value;
NotifyPropertyChanged("Product");
}
}
public string Title
{
get
{
return _product.Title;
}
set
{
_product.Title = value;
NotifyPropertyChanged("Title");
}
}
public string ScanCode
{
get
{
return _product.ScanCode;
}
set
{
_product.ScanCode = value;
NotifyPropertyChanged("ScanCode");
}
}
public string Manufacturer
{
get
{
return _product.Manufacturer;
}
set
{
_product.Manufacturer = value;
NotifyPropertyChanged("Manufacturer");
}
}
public string ManufacturerNumber
{
get
{
return _product.ManufacturerNumber;
}
set
{
_product.ManufacturerNumber = value;
NotifyPropertyChanged("ManufacturerNumber");
}
}
public string Description
{
get
{
return _product.Description;
}
set
{
_product.Description = value;
NotifyPropertyChanged("Description");
}
}
#endregion
#region Commands
private ICommand _saveCommand;
public ICommand SaveCommand
{
get
{
return _saveCommand;
}
set
{
_saveCommand = value;
}
}
#endregion
#region Command Executions
public void Save(object obj)
{
using (var db = new InvContext())
{
db.Products.Attach(Product);
db.Entry(Product).State = Product.ProductId == 0 ?
EntityState.Added : EntityState.Modified;
db.SaveChanges();
}
MessageBox.Show("Product saved: " + Product.Title);
}
#endregion
#region Constructors
public ProductViewModel()
{
if (_product == null)
{
_product = new Product();
}
SaveCommand = new RelayCommand(new Action<object>(Save));
}
#endregion
}
}
Here the ProductView.xaml view:
<UserControl x:Class="Inventory.Desktop.Views.ProductView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="450">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Name="SaveProductButton" Command="{Binding SaveCommand}" Content="Speichern" Margin="3" BorderThickness="0">
</Button>
</StackPanel>
<StackPanel DockPanel.Dock="Top" VerticalAlignment="Stretch">
<Label Content="Scan Code" />
<TextBox Text="{Binding Path=ScanCode}" HorizontalAlignment="Stretch" Margin="3" Padding="3" Height="50" TextAlignment="Right">
<TextBox.Background>
<ImageBrush ImageSource="..\Images\Barcode32.png" AlignmentX="Left" Stretch="None" />
</TextBox.Background>
</TextBox>
<Label Content="Bezeichnung" />
<TextBox Text="{Binding Path=Title, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Hersteller" />
<TextBox Text="{Binding Path=Manufacturer, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Hersteller Nummer" />
<TextBox Text="{Binding Path=ManufacturerNumber, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
<Label Content="Beschreibung / Information" />
<TextBox Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="3" />
</StackPanel>
</DockPanel>
</UserControl>
and here the code-behind ProductView.xaml.cs:
namespace Inventory.Desktop.Views
{
/// <summary>
/// Interaktionslogik für ProductView.xaml
/// </summary>
public partial class ProductView : UserControl
{
ProductViewModel _productModel = new ProductViewModel();
public ProductView()
{
InitializeComponent();
base.DataContext = _productModel;
}
}
}
What's currently working:
When I click a button, I got a new TabItem displaying the correct view and all commands work correctly.
What's not working:
When I open a TabItem, enter some information, and then I open another TabItem with a different ViewModel, switching the focus to the new TabItem and then back to the original oen, then all entered information are gone (object is null).
When I open a TabItem, enter some information, and then I open another TabItem with the same ViewModel, then both TabItems show the the same information.
When I add a new TabItem, it doesn't get focus.
I am totally lost and I hope you can tell me what I am doing wrong.
Best
Stefan
Have a property on your ViewModel to store the reference to current/selected tab
public WorkspaceViewModel SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
RaisePropertyChanged(() => SelectedTab);
}
}
and bind this to SelectedItem property on TabControl.
<TabControl ItemsSource="{Binding Workspaces}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}"
Margin="3"
DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
And finally, you want to update SelectedTab property whenever you are adding a new tab. Modify your AddProduct like this:
public void AddProduct(object obj)
{
var workspace = new ProductViewModel();
Workspaces.Add(workspace);
SelectedTab = workspace;
}

MVVM viewmodel events (commands?)

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.

Categories

Resources