I need some suggestions, help to solve any problems. I have to create a view with a dynamic count of ContentPages. I created two ViewModels, one with a logic and a stop watch and a second one to control the dynamic pages. It looks like this:
CircleViewModel:
public class CircleViewModel: ViewModelBase {
public ObservableCollection<Circle> Circles { get; set; }
private string _timeText;
public string TimeText {
get => _timeText;
set {
_timeText = value;
OnPropertyChanged();
}
// Some ICommands and methods
public async Task StartStopWatch() { ... }
}
MultiPageViewModel:
public class MultiPageViewModel : ViewModelBase {
public ObservableCollection<CircleViewModel> Pages { get; set; }
private CircleViewModel _currentPage;
public CircleViewModel CurrentPage {
get => _currentPage;
set {
_currentPage = value;
OnPropertyChanged();
}
// Some ICommands and methods
}
MultiPageView.xaml:
<ContentPage ...
BindingContext="{Binding MultiPageViewModel, Source={StaticResource Locator}}">
<Grid>
<ListView ItemsSource="{Binding CurrentPage.Circles}"/>
<Label Text="{Binding CurrentPage.Title}"/>
<Button Text="{Binding CurrentPage.TimeText}" Command="{Binding CurrentPage.StartWatchCommand}"/>
<userControls:VerticalTabView ItemsSource="{Binding MultiPageViewModel.Pages, Source={StaticResoruce Locator}}"
MoreButtonCommand="{Binding MultiPageViewModel.MoreButtonCommand, Source={StaticResource Locator}}"/>
</Grid>
</ContentPage>
VerticalTabView is a user control to change the current page.
I did'nt like the usability, so I changed the ContentPage to CarouselPage. With UWP it works fine, with Android the scrolling starts to stutter, in the output of Visual Studio appears "The application may be doing too much work on its main thread." and crashes with an OutOfMemory Exception. The CarouselPage does not work with iOS currently.
How can I improve this? I am grateful for any advice. Thank you.
Related
I have a wpf application that I want to be able to launch a separate window in which I will have a listview bound to an observable collection. However I am unable to get the collection values to appear in the list view. Here is some of the code.
Window (Named WizardView):
(Data context defined like so at top of xaml):
d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel}"
<Border Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" BorderBrush="Black">
<ListView BorderThickness="0" ItemsSource="{Binding TestModel.FailedTests}">
<Label Content="Introduction" FontWeight="Bold" />
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding }"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
MainViewModel Code:
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
TestModel = new TestViewModel();
WizardModel = new WizardViewModel(TestModel);
}
private WizardViewModel _wizardModel;
public WizardViewModel WizardModel
{
get
{
return _wizardModel;
}
set
{
_wizardModel = value;
RaisePropertyChanged();
}
}
private TestViewModel _testViewModel;
public TestViewModel TestModel
{
get
{
return _testViewModel;
}
set
{
_testViewModel = value;
RaisePropertyChanged();
}
}
WizardViewModel Code:
public class WizardViewModel : TestViewModel
{
internal TestViewModel TestModel;
public WizardViewModel(TestViewModel testModel)
{
TestModel = testModel;
(TroubleShootCommand is defined in seperate UC, and launches fine)
TestModel.TroubleShootCommand = new DelegateCommand(Load, CanTroubleShoot);
}
public void Load()
{
(Sync Root is used because it is running on worker thread. Issue somewhere here?)
_syncRoot.Send(o =>
{
var troubleShootWizard = new WizardView();
troubleShootWizard.Owner = Application.Current.MainWindow;
troubleShootWizard.ShowDialog();
}, null);
}
Observable Collection in TestViewModel (Initialized in ctor):
private ObservableCollection<string> _failedTests;
public ObservableCollection<string> FailedTests
{
get { return _failedTests; }
set
{
_failedTests = value;
RaisePropertyChanged();
}
}
Any Help is appreciated, I feel like I have tried everything. I have watched values through the watch window under TestModel.FailedTests in the collection right before and right after launch.
First,
(Data context defined like so at top of xaml): d:DataContext="{d:DesignInstance Type=viewModels:MainViewModel}"
This is a mistake, this way d: you are defining the DataContext at design time..
You can create the viewmodel inside .xaml this way:
<WizardView.DataContext>
<viewModels:MainViewModel/>
</WizardView.DataContext>
Using the design time declaration can help in many ways like knowing the viewmodel in case you are creating it and assigning it in C# (or via a IoC mechanism), also it helps tools like IntelliSense and ReSharper to analyze your bindings and warn you if you misspell a property's name in xaml, auto-completion, etc... (more on this can be found here, and here)
Second, if you are assigning the WizardViewModel in your .xaml the same way (i.e. design-time), then you can either do it in your Load() function (add troubleShootWizard.DataContext = this;) or assign it in .xaml the same way I've mentioned before.
I have a Xamarin project for Android and UWP. This issue seems to only happen on UWP.
In my Xamarin project I have ContentPage with a view model bound as context. In this ViewModel there's an ObservableCollection with another kind of view model. When I create a new instance of this underlying ViewModel and add to my ObservableCollection, sometimes the ContentPage works as expected, showing an item in my ListView. But sometimes there's an empty element added, that I can see when hovering over the list. When this happens I get a bunch of warnings in the Output tab.
My DownloadsPage:
<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"
xmlns:local="clr-namespace:Downloader.Converters"
mc:Ignorable="d"
x:Class="Downloader.Views.DownloadsPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:DownloadStatusToColorConverter x:Key="downloadStatusToColor" />
</ResourceDictionary>
</ContentPage.Resources>
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<ListView x:Name="DownloadsListView" SelectionMode="None" ItemsSource="{Binding Downloads}" RowHeight="70">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10" BackgroundColor="{Binding DownloadStatus, Converter={StaticResource downloadStatusToColor}}">
<Label Text="{Binding Name}"
d:Text="{Binding .}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="16" />
<Grid Grid.Row="0" Grid.Column="0" Padding="10,0,10,0">
<ProgressBar BackgroundColor="Transparent" Progress="{Binding PercentDownloaded}" HorizontalOptions="FillAndExpand" HeightRequest="20">
</ProgressBar>
<Label Text="{Binding PercentString}" HorizontalTextAlignment="Center"></Label>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RefreshView>
</ContentPage>
DownloadsViewModel is set as context in the code-behind like this:
public partial class DownloadsPage : ContentPage
{
private readonly DownloadsViewModel _viewModel;
public DownloadsPage()
{
InitializeComponent();
BindingContext = _viewModel = new DownloadsViewModel();
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
Device.BeginInvokeOnMainThread(() => _viewModel.RefreshDownloads());
return true;
});
}
}
The bound DownloadsViewModel:
public class DownloadsViewModel : BaseViewModel
{
public ObservableCollection<DownloadViewModel> Downloads { get; set; } = new ObservableCollection<DownloadViewModel>();
public Command LoadItemsCommand { get; set; }
public DownloadsViewModel()
{
Title = "Downloads";
LoadItemsCommand = new Command(() => {
IsBusy = true;
Downloads.Clear();
RefreshDownloads();
IsBusy = false;
});
}
public void RefreshDownloads()
{
foreach (var download in DownloadManager.GetDownloads())
{
var existingDownload = Downloads.FirstOrDefault(d => d.Id == download.Id);
if (existingDownload != null)
{
existingDownload.UpdateValues(download);
}
else
{
Downloads.Add(new DownloadViewModel(download));
}
}
}
}
And the ObservableCollection contains DownloadViewModel that looks like this:
public class DownloadViewModel : BaseViewModel
{
private IDownload _download;
public DownloadViewModel(IDownload download)
{
UpdateValues(download);
}
private string _id;
public string Id
{
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _name;
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
private DownloadStatus _status;
public DownloadStatus DownloadStatus
{
get { return _status; }
set { SetProperty(ref _status, value); }
}
public double PercentDownloaded
{
get
{
return _download.DownloadedBytes == -1
? 0f
: (double)_download.DownloadedBytes / _download.TotalBytes;
}
}
public string PercentString { get => $"{(int)(PercentDownloaded * 100)} %"; }
public void UpdateValues(IDownload download)
{
_download = download;
Id = _download.Id;
Name = _download.Name;
DownloadStatus = _download.Status;
}
}
The error I sometimes get which causes items in my ListView to be empty:
Binding: 'DownloadStatus' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.StackLayout.BackgroundColor'
Binding: 'Name' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
Binding: 'PercentDownloaded' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.ProgressBar.Progress'
Binding: 'PercentString' property not found on 'Downloader.ViewModels.DownloadsViewModel', target property: 'Xamarin.Forms.Label.Text'
When debugging I've confirmed that the item is added to my ObservableCollection as expcted.
How come sometimes it's looking for DownloadStatus, Name, PercentDownloaded and PercentString on DownloadsViewModel instead of DownloadViewModel?
Xamarin UWP seems to bind to the wrong view model
I checked your code sample and it works as expect. But I found the progress value does not update automatically that cause the listview item can't display, I have update the IDownload interface add PercentDownloaded property. For the testing it could works in uwp platform.
The problem was that the ViewModels did not have setters with INotifyPropertyChanged implemented for all properties. The source code is available on Github, and the commit that fixes the issue is this one.
I'm new in c# UWP development and I'm trying to change the value of a TextBlock in runtime, but the binding does not work properly.
I'm binding the text property of the TextBlock in XAML to a property on a ViewModel with INotifyPropertyChanged, and the value changes every 10 seconds.
I don't know if it's the correct way to do it, can someone help me?
Thanks in advance!
this is the ViewModel code
class MainPaigeViewModel : INotifyPropertyChanged
{
public MainPaigeViewModel()
{
Task.Run(async () =>
{
Random random = new Random();
while (true)
{
await Task.Delay(10000);
int newValue = random.Next(-40, 40);
_MyValue = newValue.ToString();
Debug.WriteLine(MyValue);
}
});
}
//Properties
private string _MyValue;
public string MyValue
{
get { return _MyValue; }
set
{
_MyValue = value;
RaisePropertyChanged("MyValue");
}
}
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
and the XAML code
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CountDown2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModels="using:CountDown2.ViewModels"
x:Class="CountDown2.MainPage"
mc:Ignorable="d">
<Page.DataContext>
<ViewModels:MainPaigeViewModel/>
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<RelativePanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="{Binding MyValue, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
Width="100"
Height="40"
TextAlignment="Center"
FontSize="20"
/>
</RelativePanel>
</Grid>
</Page>
In UWP unlike silver light and WPF the default binding is One time for performance reasons. The Binding only takes place once as the application starts up. One way binding is the default of WinRT, Silverlight and wpf. Meaning the view will be updated but updating the view will not update view model. Two way binding will update both the view and the view model.
So for a <TextBlock> in the example, it is recommended to use One Way binding.
In a <TextBox> it is recommended to use Two Way binding for user input.
I found a couple small bugs that were causing the binding to fail ... so I changed the viewmodel... The private property was being used rather than public one. Since the code is updating the value in a thread, and then trying to marshal the objects across threads, a dispatcher was added. Also added a common base class for all view models. This make property binding a little easier, it stops binding issues when refactoring property names.
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync
public class MainPaigeViewModel: ViewModelBase
{
public MainPaigeViewModel()
{
Task.Run(async () =>
{
Random random = new Random();
while (true)
{
await Task.Delay(1000);
int newValue = random.Next(-40, 40);
try
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => {
MyValue = newValue.ToString();
});
}
catch (Exception ex)
{
string s = ex.ToString();
}
Debug.WriteLine(MyValue);
}
});
}
//Properties
private string _MyValue;
public string MyValue
{
get { return _MyValue; }
set
{
_MyValue = value;
OnPropertyChanged();
}
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I also changed the view to use x:binding. I like x:binding over the old data binding because it shows binding issues at compile time rather than at runtime. This is besides the performance enhancements it gives.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<RelativePanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="{x:Bind viewModel.MyValue, Mode=OneWay}"
Width="100"
Height="40"
TextAlignment="Center"
FontSize="20"
/>
</RelativePanel>
</Grid>
Page behind code for x:bind
public sealed partial class MainPage : Page
{
public MainPaigeViewModel viewModel;
public MainPage()
{
this.InitializeComponent();
viewModel = new MainPaigeViewModel();
}
}
Try:Text="{Binding MyValue, Mode=TwoWay}"
In my application, I need to bind a checkbox list to an observable collection. I have seen many examples but I could not find a proper implementation for this and thats why I am posting this question.
The View:
<Grid Name="GrdMain" Background="White">
<ListView Name="lstConditions" VerticalAlignment="Top" Height="150"
ItemsSource="{Binding ConditionsModels}" Margin="0,25,0,0" BorderBrush="Transparent" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=condition}" Margin="8" Style="{StaticResource CheckBoxDefault}"
IsChecked="{Binding hasCondition,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</grid>
The model:
public class ConditionsModel
{
public int profileId { get; set; }
public string condition { get; set; }
public bool hasCondition { get; set; }
}
The View Model:
public class ConditionsViewModel : INotifyPropertyChanged
{
private ConditionsModel _conditionsModel;
private ObservableCollection<ConditionsModel> _conditionsModels;
public ConditionsModel ConditionsModel
{
get
{
return _conditionsModel;
}
set
{
_conditionsModel = value;
RaisePropertyChanged("ConditionsModel");
}
}
public ObservableCollection<ConditionsModel> ConditionsModels
{
get
{
return _conditionsModels;
}
set
{
_conditionsModels = value;
RaisePropertyChanged("ConditionsModels");
}
}
public ConditionsViewModel(int profileId)
{
ConditionsModel = new ConditionsModel();
ConditionsModels = new ObservableCollection<ConditionsModel>();
ConditionsModels.CollectionChanged += ConditionsModels_CollectionChanged;
GetConditions(profileId);
}
void ConditionsModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("ConditionsModels");
}
private void GetConditions(int profileId)
{
HealthAssessmentRepository _rep = new HealthAssessmentRepository();
_conditionsModels = _rep.GetConditions(profileId);
}
}
Is this a correct implementation? I need to update the model when the user checks or unchecks the checkbox. But its not raising the propery changed event when the check box is checked or unchecked.Should I implement the INotifyPropertyChanged interface on the model as well?
I have seen many examples, but all of them has different approaches to this and I am confused. Please show the correct implementation of this?
Thanks
I think you have missed the DataType property within DataTemplate. Just refer this
<DataTemplate DataType="{x:Type sampleApp:ConditionsModel}">
Here sampleApp in the namespace reference created within tag. And ConditionsModel is your model class.
You need to implement INotifyPropertyChanged for class ConditionsModel and raise PropertyChangedEvent for the property you want to observe/synchronize, because it is ViewModel as well.
For class ConditionsViewModel, it's the ViewModel of whole ListView, for ConditionsModel, it's the ViewModel of every line. ViewModel can be overlaid. If ConditionsModel is the domain model, my suggestion is that add a new ItemViewModel, because they belong to different layers. It's always better to distinguish the different layers properly.
I have a WPF forms and a class Users (content attributes Id, Login and Name), in my class of this forms, I had get a Users object for put this information in the form with DataContext and Binding
I can put this Users object to my Window.DataContext (this.DataContext = usersObject;) with code behind, but I think if I can make this with XAML, maybe is better
I have set a attribute in my class UserForm (public Users usersObject {get; set;})
My form UserForm : Window
<Window DataContext="{What need I put here?">
<Grid>
<TextBlock Text="Id:"/>
<TextBox Name="Id" Text="{Binding Path=Id}"/>
<TextBlock Text="Login:"/>
<TextBox Name="Login" Text="{Binding Path=Login}"/>
<TextBlock Text="Name:"/>
<TextBox Name="Name" Text="{Binding Path=Name}"/>
</Grid>
</Window>
UserForm.xaml.cs
public class UserForm : Window
{
public Users userObject { get; set; }
public UserForm(Users user)
{
InitializeComponent();
this.userObject = user;
}
}
My class Users
public class Users
{
public int Id { get; set; }
public string Login { get; set; }
public string Name { get; set; }
}
How to I do for set userObject in the itself Window.DataContext for TextBox's can put it values?
Some time back I had written an article on different options of binding XAML control to code behind property. This might help you.
http://codingseason.blogspot.in/2013/05/data-binding-xaml-control-to-property.html
Remove the DataContext binding since you are not explicitly doing MVVM pattern. No point of doing the binding.
In your Window.xaml
<Window>
<Grid>
<TextBlock Text="Id:"/>
<TextBox Name="Id" Text="{Binding Path=Id}"/>
<TextBlock Text="Login:"/>
<TextBox Name="Login" Text="{Binding Path=Login}"/>
<TextBlock Text="Name:"/>
<TextBox Name="Name" Text="{Binding Path=Name}"/>
</Grid>
Add this to your Behind the code Window.xaml.cs
public class UserForm : Window
{
public Users userObject { get; set; }
public UserForm(Users user)
{
InitializeComponent();
this.DataContext = user;
}
}
If you would do it in MVVM you would have done something like this
ViewModel.cs
public class UserViewModel
{
private Users _model;
public UserViewModel(Users model)
{
_model = model;
}
public int Id { get { return _model.Id; } }
public string Login { get { return _model.Login; } set { _model.Login; } }
public string Name { get { return _model.Name; } set { _model.Name; } }
}
Now the ViewModel can be customize depending on what you need, you can expose the Model, inject it to the constructor or just set the property value if it is exposed. Don't forget to implement INotifyPropertyChanged if you want to propagate any values in the ViewModel back to the user interface.
View.xaml.cs
public class UserForm : Window
{
public UserForm(Users user)
{
InitializeComponent();
this.DataContext = new UserViewModel(user);
}
View.xaml
You have two choices, you can explicitly set the DataContext just like what I did behind the code or you can create a public property that returns the UserViewModel. It's just the same
Model.cs
public class Users { //Whatever properties you need }
Now this is a very simplistic example of MVVM pattern. Once you know the basics, you can then integrate some helpful frameworks that implements MVVM for you like Caliburn Micro and MVVM Toolkit
Create an object of UserForm and assigning data context is pretty simple.
UserForm userFormView = new UserForm ();
Users dataContextObject = new Users();
userFormView.DataContext = dataContextObject;
userFormView.Show() //you can also use showdialog. Thats up to you
Its better you remove the following code:
public Users userObject { get; set; }
public UserForm(Users user)
{
InitializeComponent();
this.userObject = user;
}
Its better to separate the view and the viewmodel. Have a look at MVVM and it will make more sense
This is one of the way to assign DataContext directly from xaml.
Example App.xaml
<Application.Resources>
<local:Users x:Key="Users"/>
</Application.Resources>
Example UserForm.xaml
<Window DataContext="{StaticResource Users}">
<Grid>
<TextBlock Text="Id:"/>
<TextBox Name="Id" Text="{Binding Path=Id}"/>
<TextBlock Text="Login:"/>
<TextBox Name="Login" Text="{Binding Path=Login}"/>
<TextBlock Text="Name:"/>
<TextBox Name="Name" Text="{Binding Path=Name}"/>
</Grid>
</Window>
Maybe you can define a DependencyProperty to wrap the access to the userObject,
then bind it to the DataContext