I am struggling in getting my head around how to model bind nested views within xamarin and MVVM.
The idea is to make a custom control. This will then be on a view. This will be in a page.
I have created a basic project with some buttons to try and get it working. The page has a dynamic grid. So in the code behind it loops through and generates the view. The page also has a "SelectedView" property. When a button is clicked I want to be able to update this property so I could update some information on the page.
Here is my code to help give an idea to what I am doing (I maybe thinking about this wrong).
Page
<ContentPage.Content>
<Grid x:Name="grid">
</Grid>
</ContentPage.Content>
Code behind
LabelPageViewModel VM { get; set; }
public LabelPage()
{
InitializeComponent();
VM = new LabelPageViewModel();
VM.InitiliseGridContent();
BindingContext = VM;
grid.RowDefinitions = new RowDefinitionCollection();
for(int i = 0; i < 5; i++)
{
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
}
grid.ColumnDefinitions = new ColumnDefinitionCollection();
for (int i = 0; i < 5; i++)
{
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
}
int listValue = 0;
for(int i = 0;i <4; i++)
{
for (int j = 0; j < 4; j++)
{
grid.Children.Add(VM.GridViewList[listValue],j,i);
listValue = listValue + 1;
}
}
}
ViewModel
public ObservableCollection<CustomButtonGridView> GridViewList { get; set; }
public CustomButtonGridView selectedView;
public CustomButtonGridView SelectedView
{
get { return selectedView; }
set
{
if(selectedView != value)
{
selectedView = value;
OnPropertyChanged();
}
}
}
Color backgroundColor;
public Color BackgroundColor
{
get
{
return backgroundColor;
}
set
{
if(backgroundColor!= value)
{
backgroundColor = value;
OnPropertyChanged();
}
}
}
public LabelPageViewModel()
{
}
public void InitiliseGridContent()
{
GridViewList = new ObservableCollection<CustomButtonGridView>();
//Loop through and create blank items.
for (int i = 0; i < 16; i++)
{
GridViewList.Add(new CustomButtonGridView(this));
}
SelectedView = GridViewList[0];
}
public ICommand BtnCmd
{
get { return new Command<CustomButtonGridView>(async (x) => await ButtonClicked(x)); }
}
async Task ButtonClicked(CustomButtonGridView model)
{
SelectedView = model;
foreach(var item in GridViewList)
{
if(item != SelectedView)
{
BackgroundColor = Color.Red;
}
else
{
BackgroundColor = Color.Blue;
}
}
}
View
<Grid x:Name="grid">
<buttonView:CustomButtonView x:Name="myButton"
Command="{Binding BtnCmd}"
CommandParameter="{x:Reference myContent}" Text="Button"
BackgroundColor="{Binding BackgroundColor}"></buttonView:CustomButtonView>
</Grid>
Code Behind
ButtonGridViewModel VM { get; set; }
LabelPageViewModel labelVM { get; set; }
public CustomButtonGridView(LabelPageViewModel vm)
{
InitializeComponent();
labelVM = vm;
BindingContext = labelVM;
grid.RowDefinitions = new RowDefinitionCollection();
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
}
public static readonly BindableProperty SelectedCommandProperty =
BindableProperty.Create(nameof(SelectCmd), typeof(ICommand), typeof(CustomButtonGridView), null);
public ICommand SelectCmd
{
get => (ICommand)GetValue(SelectedCommandProperty);
set
{
SetValue(SelectedCommandProperty, value);
labelVM.selectedView = this;
}
}
public static void Execute(ICommand command)
{
if (command == null) return;
if (command.CanExecute(null))
{
command.Execute(null);
}
}
// this is the command that gets bound by the control in the view
// (ie. a Button, TapRecognizer, or MR.Gestures)
public Command OnTap => new Command(() => Execute(SelectCmd));
ViewModel
public ICommand BtnCmd
{
get { return new Command(async () => await ButtonClicked()); }
}
async Task ButtonClicked()
{
Application.Current.MainPage.DisplayAlert("Clicked", "Clicked", "OK");
}
public Color selectedColor;
public Color SelectedColor
{
get { return selectedColor; }
set
{
if(selectedColor != value)
{
selectedColor = value;
OnPropertyChanged();
}
}
}
Control
public class CustomButtonView : Button
{
public static readonly BindableProperty CustomTextProperty = BindableProperty.Create("CustomText", typeof(string), typeof(CustomButtonView), "Default");
public string CustomText
{
get { return (string)base.GetValue(CustomTextProperty); }
set
{
base.SetValue(CustomTextProperty, value);
Text = value;
}
}
public CustomButtonView()
{
Text = CustomText;
}
}
On my page I have a list of the Views. I then loop through and create 16 items on a 4X4 grid.
I set the first item to be the selected item.
On my View I have my custom button where I could add it's property bindings etc.
I then have a click event in the view that fires an alert. I want to change the color of the clicked button as a proof of concept. Currently all buttons turn red as none of them are marked as the "selectedItem".
What I am trying to achieve
Button is clicked on control -> notifies view -> notifies Page
The button can do an action. The view could do some logic. The page could do some logic based on button pressed.
I am not sure how to glue together multiple views and view models to get them to bind correctly. Maybe some kind of 2 way binding I am missing?
I might be thinking about this wrong and there might be a better way to achieve what I want.
Any help would be appreciated.
Many thanks
According to your description, you can try CollectionView which is easy to achieve this funciton.
CollectionView is a view for presenting lists of data using different layout specifications. It aims to provide a more flexible, and performant alternative to ListView.
CollectionView should be used for presenting lists of data that require scrolling or selection. A bindable layout can be used when the data to be displayed doesn't require scrolling or selection.
The result is like this:
For more , check: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/collectionview/introduction .
Related
Is it possible to have one ViewModel for multiple dynamic Tabs? Meaning that, whenever I create a new tab, it should use the same instance of ViewModel so I can retrieve information and also prevent each Tab from sharing data/showing the same data.
The setting I'm thinking of using it in would be for a payroll application where each employee's payslip can be updated from each tab. So the information should be different in each Tab.
Is this possible?
Update: Added code
MainViewModel where Tabs Collection is handled:
public ObservableCollection<WorkspaceViewModel> Workspaces { get; set; }
public MainViewModel()
{
Workspaces = new ObservableCollection<WorkspaceViewModel>();
Workspaces.CollectionChanged += Workspaces_CollectionChanged;
}
void Workspaces_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (WorkspaceViewModel workspace in e.NewItems)
workspace.RequestClose += this.OnWorkspaceRequestClose;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (WorkspaceViewModel workspace in e.OldItems)
workspace.RequestClose -= this.OnWorkspaceRequestClose;
}
private void OnWorkspaceRequestClose(object sender, EventArgs e)
{
CloseWorkspace();
}
private DelegateCommand _exitCommand;
public ICommand ExitCommand
{
get { return _exitCommand ?? (_exitCommand = new DelegateCommand(() => Application.Current.Shutdown())); }
}
private DelegateCommand _newWorkspaceCommand;
public ICommand NewWorkspaceCommand
{
get { return _newWorkspaceCommand ?? (_newWorkspaceCommand = new DelegateCommand(NewWorkspace)); }
}
private void NewWorkspace()
{
var workspace = new WorkspaceViewModel();
Workspaces.Add(workspace);
SelectedIndex = Workspaces.IndexOf(workspace);
}
private DelegateCommand _closeWorkspaceCommand;
public ICommand CloseWorkspaceCommand
{
get { return _closeWorkspaceCommand ?? (_closeWorkspaceCommand = new DelegateCommand(CloseWorkspace, () => Workspaces.Count > 0)); }
}
private void CloseWorkspace()
{
Workspaces.RemoveAt(SelectedIndex);
SelectedIndex = 0;
}
private int _selectedIndex = 0;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
OnPropertyChanged("SelectedIndex");
}
}
WorkspaceViewModel:
public PayslipModel Payslip { get; set; }
public WorkspaceViewModel()
{
Payslip = new PayslipModel();
SaveToDatabase = new DelegateCommand(Save, () => CanSave);
SelectAll = new DelegateCommand(Select, () => CanSelect);
UnSelectAll = new DelegateCommand(UnSelect, () => CanUnSelect);
}
public ICommand SaveToDatabase
{
get; set;
}
private bool CanSave
{
get { return true; }
}
private async void Save()
{
try
{
MessageBox.Show(Payslip.Amount.ToString());
}
catch (DbEntityValidationException ex)
{
foreach (var en in ex.EntityValidationErrors)
{
var exceptionDialog = new MessageDialog
{
Message = { Text = string.Format("{0}, {1}", en.Entry.Entity.GetType().Name, en.Entry.State) }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
foreach (var ve in en.ValidationErrors)
{
exceptionDialog = new MessageDialog
{
Message = { Text = string.Format("{0}, {1}", ve.PropertyName, ve.ErrorMessage) }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
}
}
}
catch (Exception ex)
{
var exceptionDialog = new MessageDialog
{
Message = { Text = string.Format("{0}", ex) }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
}
}
public event EventHandler RequestClose;
private void OnRequestClose()
{
if (RequestClose != null)
RequestClose(this, EventArgs.Empty);
}
private string _header;
public string Header
{
get { return _header; }
set
{
_header = value;
OnPropertyChanged("Header");
}
}
Payroll UserControl where WorkspaceViewModel is DataContext:
public Payroll()
{
InitializeComponent();
DataContext = new WorkspaceViewModel();
}
Payroll.xaml Tabcontrol:
<dragablz:TabablzControl ItemsSource="{Binding Workspaces}" SelectedIndex="{Binding SelectedIndex}" BorderBrush="{x:Null}">
<dragablz:TabablzControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</dragablz:TabablzControl.ItemTemplate>
<dragablz:TabablzControl.ContentTemplate>
<DataTemplate>
<ContentControl Margin="16">
<local:TabLayout DataContext="{Binding Path=Payslip, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" x:Name="tabLayout"/>
</ContentControl>
</DataTemplate>
</dragablz:TabablzControl.ContentTemplate>
</dragablz:TabablzControl>
This works as expected, each tab displays different info and bindings work okay. However, I'm unable to retrieve the info in the MessageBox.
I'm not sure if I totally understand your question but if you need a Window with a tabcontrol, in which each tab refers to an employee, then you will have to bind the ItemsSource of the tabcontrol to a list of the ViewModel.
It is not possible to bind all tabpages to the same instance because then the tabpages will all do the same, and show the same information.
I couldn't get it to work the way I had it, so I placed the save button inside the view that has DataContext set to where employee's info are loaded and got it to work from there, since it directly accesses the properties.
ViewModels should have a 1:1 relationship with the model. In your TabControl's DataContext, let's say you have properties like:
public ObservableCollection<EmployeeViewModel> Employees {get;set;}
public EmployeeViewModel CurrentEmployee
{
get { return _currentEmployee;}
set
{
_currentEmployee = value;
OnPropertyChanged("CurrentEmployee");
}
}
where Employees is bound to ItemsSource of the TabControl, and CurrentEmployee to CurrentItem. To create a new tab:
var employee = new Employee();
var vm = new EmployeeViewModel(employee);
Employees.Add(vm);
CurrentEmployee = vm;
If you want a save button outside of the TabControl, just set its DataContext to CurrentEmployee.
I hope this helps!
Edit:
Two things I think are causing problems:
Payroll.xaml should be bound to MainViewModel since that's where the Workspaces collection is.
Do not instantiate ViewModels in your view's code behind. Use a DataTemplate instead (see this question).
Take a look at Josh Smith's MVVM demo app (source code)
I am using Xamarin.Forms and attempting to use the MVVM architecture.
I have a ContentPage that has a simple List View on it. The List View has its item source property bound to my ViewModel. I am able to populate the list just fine. All of the items display as they should.
When I click on an item from the list, I need to navigate to a different page based on the item selected. This is what is not working. It will ONLY work if I reference my underlying Model directly (which is NOT what I want to do)
I have the ListView.ItemSelected event coded. However, the Item Selected event can't determine what the "display_text" is of the List Item selected. How do I achieve this without having to reference my model directly from my View (Page)?
MainPage Code:
public partial class MainPage : ContentPage
{
private int intPreJobFormID = 0;
public MainPage()
{
InitializeComponent();
BindingContext = new MainPageViewModel();
Label lblHeader = new Label
{
Text = "HW Job Assessments",
FontSize = ViewGlobals.lblHeader_FontSize,
HorizontalOptions = ViewGlobals.lblHeader_HorizontalOptions,
FontAttributes = ViewGlobals.lblHeader_FontAttributes,
TextColor = ViewGlobals.lblHeader_TextColor
};
//Create the Main Menu Items List View
var lvMain = new ListView
{
//Pull down to refresh list
IsPullToRefreshEnabled = true,
//Define template for displaying each item.
//Argument of DataTemplate constructor is called for each item. It must return a Cell derivative.
ItemTemplate = new DataTemplate(() =>
{
//Create views with bindings for displaying each property.
Label lblDisplayText = new Label();
lblDisplayText.SetBinding(Label.TextProperty, "display_text");
lblDisplayText.FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label));
//Return an assembled ViewCell.
return new ViewCell
{
View = new StackLayout
{
Padding = new Thickness(20, 5, 0, 0),
Orientation = StackOrientation.Horizontal,
Children =
{
new StackLayout
{
VerticalOptions = LayoutOptions.Center,
Spacing = 0,
Children =
{
lblDisplayText
}
}
}
}
};
})
};
lvMain.SetBinding(ListView.ItemsSourceProperty, "MainMenuItems");
lvMain.ItemSelected += lvMain_ItemSelected;
}
private async void lvMain_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var lv = (ListView)sender;
if (e.SelectedItem == null)
{
return; //ItemSelected is called on deselection which results in SelectedItem being set to null
}
//var item = e.SelectedItem as TableMainMenuItems; //This is what I DON'T want to use because it references my Model directly.
var item = e.SelectedItem;
switch (item.display_text) //This is what I need. I can't get this unless I reference my Model "TableMainMenuItems" directly.
{
case "Forms List":
await Navigation.PushAsync(new FormsListPage());
break;
case "New Pre-Job":
await Navigation.PushAsync(new PreJobPage(intPreJobFormID));
break;
}
//Comment out if you want to keep selections
lv.SelectedItem = null;
}
}
MainPageViewModel Code:
public class MainPageViewModel
{
public int intPreJobFormID = 0;
private DatabaseApp app_database = ViewModelGlobals.AppDB;
private DatabaseFormData formdata_database = ViewModelGlobals.FormDataDB;
private IEnumerable<TableMainMenuItems> lstMaineMenuItems;
private IEnumerable<TableFormData> lstRecentJobs;
public string DisplayText { get; set; }
public IEnumerable<TableMainMenuItems> MainMenuItems
{
get { return lstMaineMenuItems; }
set
{
lstMaineMenuItems = value;
}
}
public IEnumerable<TableFormData> RecentJobs
{
get { return lstRecentJobs; }
set
{
lstRecentJobs = value;
}
}
public MainPageViewModel()
{
intPreJobFormID = app_database.GetForm(0, "Pre-Job Assessment").Id;
MainMenuItems = app_database.GetMainMenuItems();
RecentJobs = formdata_database.GetFormAnswersForForm(intPreJobFormID).OrderByDescending(o => o.date_modified);
}
}
There are 2 ways to retrieve the bound property of the item selected.
I personally prefer to handle the logic in the view because it keeps the code simpler.
1. Handle Logic In The View
cast item as your model type:
var item = e.SelectedItem as TableMainMenuItems;
async void lvMain_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var listView = (ListView)sender;
listView.SelectedItem = null;
if (e?.SelectedItem is TableMainMenuItems item)
{
switch (item.display_text)
{
case "Forms List":
await Navigation.PushAsync(new FormsListPage());
break;
case "New Pre-Job":
await Navigation.PushAsync(new PreJobPage(intPreJobFormID));
break;
}
}
}
2. Handle Logic In The View Model
View
Use a Command<T> to loosely couple the ItemSelected logic between the View and the View Model. Create an event in the View Model that will fire when the View Model has completed the logic.
public partial class MainPage : ContentPage
{
public MainPage()
{
var viewModel = new MainPageViewModel();
BindingContext = viewModel;
...
viewModel.NavigationRequested += (s,e) => Device.BeginInvokeOnMainThread(async () => await Navigation.PushAsync(e));
}
...
void lvMain_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var listView = (ListView)sender;
listView.SelectedItem = null;
var viewModel = (MainPageViewModel)BindingContext;
viewModel.ListViewItemSelectedCommand?.Invoke(e);
}
}
ViewModel
public class MainPageViewModel
{
//...
Command<SelectedItemChangedEventArgs> _listViewItemSelectedCommand;
//...
public event EventHandler<Page> NavigationRequested;
//...
public Command<SelectedItemChangedEventArgs> ListViewItemSelectedCommand => _listViewItemSelectedCommand ??
(_listViewItemSelectedCommand = new Command<SelectedItemChangedEventArgs>(ExecuteListViewItemSelectedCommand));
//...
void ExecuteListViewItemSelectedCommand(SelectedItemChangedEventArgs e)
{
var item = e as TableMainMenuItems;
switch (item?.display_text)
{
case "Forms List":
OnNavigationRequested(new FormsListPage());
break;
case "New Pre-Job":
OnNavigationRequested(new PreJobPage(0));
break;
}
}
void OnNavigationRequested(Page pageToNavigate) => NavigationRequested?.Invoke(this, pageToNavigate);
//...
}
I've created a custom usercontrol in my application and I try to bind to its property (BindableProperty) to ViewModel but it doesn't work for me. Am I doing something wrong?
This is the usercontrol. It is just custom stepper for the purpose of test project: "decrease" and "increase" buttons and quantity label between them.
Please notice that binding inside the usercontrol works perfect (for example Commands bindings) . What I am trying to do is bind QuantityProperty to ViewModel
namespace UsercontrolBindingTest.Usercontrols
{
using System.Windows.Input;
using Xamarin.Forms;
public class CustomQuantityStepper : ContentView
{
public static readonly BindableProperty QuantityProperty =
BindableProperty.Create(nameof(Quantity), typeof(int), typeof(CustomQuantityStepper), 0, BindingMode.TwoWay);
public int Quantity
{
get
{
return (int)base.GetValue(QuantityProperty);
}
set
{
base.SetValue(QuantityProperty, value);
this.OnPropertyChanged(nameof(this.Quantity));
}
}
public ICommand DecreaseQuantityCommand { get; private set; }
public ICommand IncreaseQuantityCommand { get; private set; }
public CustomQuantityStepper()
{
this.BindingContext = this;
this.DecreaseQuantityCommand = new Command(() => this.Quantity--);
this.IncreaseQuantityCommand = new Command(() => this.Quantity++);
this.DrawControl();
}
private void DrawControl()
{
var quantityEntry = new Entry();
quantityEntry.SetBinding(Entry.TextProperty, new Binding("Quantity", BindingMode.TwoWay));
quantityEntry.WidthRequest = 50;
quantityEntry.HorizontalTextAlignment = TextAlignment.Center;
var increaseQuantityButton = new Button { Text = "+" };
increaseQuantityButton.SetBinding(Button.CommandProperty, "IncreaseQuantityCommand");
var decreaseQuantityButton = new Button { Text = "-" };
decreaseQuantityButton.SetBinding(Button.CommandProperty, "DecreaseQuantityCommand");
var ui = new StackLayout()
{
Orientation = StackOrientation.Horizontal,
Children =
{
decreaseQuantityButton,
quantityEntry,
increaseQuantityButton
}
};
this.Content = ui;
}
}
}
The view with proof that binding between View and VM is working:
namespace UsercontrolBindingTest
{
using Usercontrols;
using Xamarin.Forms;
public class App : Application
{
public App()
{
MainPage = new ContentPage
{
BindingContext = new MainPageVM(),
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children =
{
this.GetLabel("Title"),
this.GetCustomStepper(),
this.GetLabel("SelectedQuantity")
}
}
};
}
private Label GetLabel(string boundedPropertyName)
{
var ret = new Label();
ret.HorizontalOptions = LayoutOptions.CenterAndExpand;
ret.SetBinding(Label.TextProperty, new Binding(boundedPropertyName));
return ret;
}
private CustomQuantityStepper GetCustomStepper()
{
var ret = new CustomQuantityStepper();
var dataContext = this.BindingContext as MainPageVM;
ret.SetBinding(CustomQuantityStepper.QuantityProperty, new Binding("SelectedQuantity", BindingMode.TwoWay));
return ret;
}
}
}
And my simple ViewModel:
namespace UsercontrolBindingTest
{
using System.ComponentModel;
internal class MainPageVM : INotifyPropertyChanged
{
private int _selectedQuantity;
public int SelectedQuantity
{
get
{
return this._selectedQuantity;
}
set
{
this._selectedQuantity = value;
this.NotifyPropertyChanged(nameof(this.SelectedQuantity));
}
}
public string Title { get; set; } = "ViewModel is bound";
public MainPageVM()
{
this.SelectedQuantity = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
I've checked plenty of different topis and blog posts but I wasn't able to find solutions for my issue. Hope for help here...
Attached sample project here: https://1drv.ms/u/s!Apu16I9kXJFtl28YBjkfwDztT9j0
You are setting the BindingContext of CustomQuantityStepper to itself (this). That's why the binding engine is looking for a public property named "SelectedQuantity" in your custom control and not in the ViewModel.
Don't set the BindingContext in the control and instead let it use the context that is currently defined and (hopefully) points to the VM.
Set the Source property of the Binding and let it point to the correct source (which would be the VM).
I got some problem in showing download percentage in GridView of WCF. I used MVVM pattern.
Here is my background worker in application start:
public partial class MainWindow : Window
{
public MainWindow()
{
Overall.EverythingOk = "Nothing";
InitializeComponent();
//IRepo repo = new Repo();
ViewModel.MainWindowsViewModel viewModel = new ViewModel.MainWindowsViewModel();
this.DataContext = viewModel;
BackGroundThread bgT = new BackGroundThread();
bgT.bgWrk.RunWorkerAsync();
}}
Here is the DoWork function in BackGroundTHread class
public void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (!Overall.stopStatus)
{
for (int i=0; i < 10000; i++)
{
Overall.PercentageDwnd = i;
Overall.caseRefId = "999999";
if (i == 9998)
{
i = 1;
}
}
}
}
Overall.PercentageDwnd and Overall.caseRefId are static variable (you can call from everywhere in the application) and always update until the background worker completed. I got another ViewModel called TestViewModel and here it is.
public class TestViewModel:BindableBase
{
private String _UpdatePer=Overall.PercentageDwnd.ToString();
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
private ObservableCollection _ViewAKA = new ObservableCollection();
private tblTransaction model;
public TestViewModel(tblTransaction model)
{
// TODO: Complete member initialization
}
public ObservableCollection ViewAKA
{
get { return _ViewAKA; }
set { SetProperty(ref _ViewAKA, value); }
}
}
I bind with TestView.xaml file
<Window x:Class="EmployeeManager.View.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestView" Height="359.774" Width="542.481">
<Grid Margin="0,0,2,0">
<Label Content="{Binding UpdatePercentage,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Background="Red" Foreground="White" Margin="130,86,0,0" VerticalAlignment="Top" Width="132" Height="39">
</Label>
</Grid>
</Window>
There is no real time update at Label even though I bind UpdatePercentage to it. How can I update real time to label?
The problem is that you are updating the static properties, which are not bound to anything. You need to update and raise the property changed notification for the properties which are bound to the label controls, i.e. UpdatePercentage
Can you pass the TestViewModel instance into the RunWorkerAsync call?
bgT.bgWrk.RunWorkerAsync(testViewModel);
And then access in the DoWork event handler:
public void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (!Overall.stopStatus)
{
var viewModel = e.Argument as TestViewModel;
for (int i=0; i < 10000; i++)
{
Overall.PercentageDwnd = i;
viewModel.UpdatePercentage = i;
Overall.caseRefId = "999999";
if (i == 9998)
{
i = 1;
}
}
}
}
Here is answer link:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/02a7b9d1-1c26-4aee-a137-5455fee175b9/wpf-percentage-status-shown-in-label-mvvm?forum=wpf
i need to trigger when the Overall.PercentageDwnd property changes.
Edited
In Overall Class:
public class Overall
{
private static int _percentage;
public static int PercentageDwnd
{
get { return _percentage; }
set
{
_percentage = value;
//raise event:
if (PercentageDwndChanged != null)
PercentageDwndChanged(null, EventArgs.Empty);
}
}
public static string caseRefId { get; set; }
public static bool stopStatus { get; set; }
public static event EventHandler PercentageDwndChanged;
}
In TestViewModel:
public class TestViewModel : BindableBase
{
private String _UpdatePer = Overall.PercentageDwnd.ToString();
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
public TestViewModel(tblTransaction model)
{
Overall.PercentageDwndChanged += Overall_PercentageDwndChanged;
// TODO: Complete member initialization
}
private void Overall_PercentageDwndChanged(object sender, EventArgs e)
{
this.UpdatePercentage = Overall.PercentageDwnd.ToString();
}
}
Since you have bound the TextBlock in the view to the UpdatePercentage source property, you need to set this one and raise the PropertyChanged event whenever you want to update the Label in the view. This means that you need to know when the Overall.PercentageDwnd property changes.
Credit to
Magnus (MM8)
(MCC, Partner, MVP)
Thanks All
I'm trying to bind a TextBlock using INotifyPropertyChanged event. But it is not updating anything to the TextBlock. The TextBlock is blank. My goal is to update the status of items which are displayed in different rows. I need to update the TextBlock's text and color based on the status.
Could anyone tell me what is wrong with my code?
public class ItemStatus : INotifyPropertyChanged
{
string itemStatus;
Brush itemStatusColor;
public string ItemStatus
{
get { return itemStatus; }
set
{
itemStatus = value;
this.OnPropertyChanged("ItemStatus");
}
}
public Brush ItemStatusColor
{
get { return itemStatusColor; }
set
{
itemStatusColor = value;
this.OnPropertyChanged("ItemStatusColor");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(
this, new PropertyChangedEventArgs(propName));
}
}
public class Items
{
List<ItemStatus> currentItemStatus;
public List<ItemStatus> CurrentItemStatus
{
get { return currentItemStatus; }
set { currentItemStatus = value; }
}
}
public partial class DisplayItemStatus : Page
{
....
....
public DisplayItemStatus()
{
foreach (Product product in lstProductList)
{
TextBlock tbItemStatus = new TextBlock();
....
Items objItems = new Items();
Binding bindingText = new Binding();
bindingText.Source = objItems;
bindingText.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
bindingText.Path = new PropertyPath(String.Format("ItemStatus"));
tbItemStatus.SetBinding(TextBlock.TextProperty, bindingText);
Binding bindingColor = new Binding();
bindingColor.Source = objItems;
bindingColor.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
bindingColor.Path = new PropertyPath(String.Format("ItemStatusColor"));
tbItemStatus.SetBinding(TextBlock.ForegroundProperty, bindingColor);
grdItemsList.Children.Add(tbItemStatus);
}
}
private void UpdateItems_Click(object sender, MouseButtonEventArgs e)
{
int intCount = 0;
List<Product> ProductList = new List<Product>();
List<ItemStatus> ItemList = new List<ItemStatus>();
ProductList = GetProducts();
foreach (Product product in ProductList)
{
intCount++;
UpdateStatus(intCount, ItemList);
}
}
public void UpdateStatus(int intIndex, List<ItemStatus> ItemList)
{
ItemStatus status = new ItemStatus();
status.ItemStatus = strOperationStatus;
status.ItemStatusColor = brshForegroundColor;
ItemList.Add(status);
}
}
Well, the specific problem here is that you're binding the TextBlock to an Item and not the ItemStatus. But you're also doing things the hard way, you really should do the binding details in XAML. Expose a collection of ItemStatus's from your view model, and have a ListBox or something with its ItemsSource bound to the collection. Then you'll need a DataTemplate which defines the TextBlock and the bindings to the ItemStatus.
Here's a good walkthrough for it in general