Issue accessing DataContext from user control on page in frame - c#

Struggling to find a solution to this issue that I am having, I have a UWP App that I have been toying around with and for the most part have been able to work things out.
The issue I am facing is with a Hambuger Menu, I have a frame that I load a page up, which displays a gridview of items. When clicked it then displays another page in that frame.
The new page has this which I am able to display data in the page no issue at all.
public sealed partial class CreatureDossier : Page
{
private Species Cat;
public CreatureDossier()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Cat = (Species)e.Parameter;
}
}
In the XAML of this page I am putting a UserControl that I would like to use the data that is present and is where I a struggling. Every time I break on the User Control it shows the DataContext as empty (null).
I have tried
<UIControls:DossierPanel />
Really not understanding what I am needing to do to make this User Control use the DataContext, I should point out that I have this in the UserControl but as stated the DataContext is null.
<TextBlock x:Name="CatName" Text="{Binding Cat.name}" />
Now the interesting thing is that if I use a property called Cat on the Usercontrol with a normal text string and a DependencyProperty in the UserControl code behind, I can display that with this.DataContext = this in the constructor.
User Control code behind
public sealed partial class DossierPanel : UserControl
{
public Models.Species Cat { get { return this.DataContext as Models.Species; } }
public DossierPanel()
{
this.InitializeComponent();
}
}
CreatureDossier.xaml
<TextBlock x:Name="CreatureName" Text="{x:Bind Cat.name}" />
<UIControls:DossierPanel />
Any help in understanding what I need to do would be great.

Set the DataContext of the Page:
public sealed partial class CreatureDossier : Page
{
private Species Cat;
public CreatureDossier()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Cat = (Species)e.Parameter;
DataContext = Cat;
}
}
The UserControl will then inherit the DataContext from the Page (assuming that you don't explicitly set the DataContext property somewhere else) and you should be able to bind to any public property of the Species class like this:
<TextBlock x:Name="CatName" Text="{Binding name}" />
Edit:
{x:Bind} doesn't use the DataContext as a default source: https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/x-bind-markup-extension
But if you give the UserControl an x:Name:
<UIControls:DossierPanel x:Name="uc" />
...and add a setter to the Cat property:
public sealed partial class DossierPanel : UserControl
{
public Models.Species Cat { get; set; }
public DossierPanel()
{
this.InitializeComponent();
}
}
And set this one:
public sealed partial class CreatureDossier : Page
{
public CreatureDossier()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
uc.Cat = (Species)e.Parameter;
}
}
<TextBlock x:Name="CatName" Text="{Binding Cat.name, Mode=OneWay}" />

Related

how to transfer data from a second page to a third page from the button click in mainpage

I'm trying to create an app that will have a main page that has 2 buttons.
the first button opens a popup page that has 2 pickers and the user has to select some value and when done, return to the main page.
the second button on the main page opens a content page that has 2 labels that are supposed to receive data from the popup page. So it's supposed to be like a final report page.
I would appreciate It if someone could provide me with a simple code preferable using MVVM. I have already created a similar app but having the main page as a MasterDetailPage and it works perfectly, the only issue I have now is, I would like to have the main page as a content page instead of masterdetailpage. I hope what I wrote is understandable
mainpage xaml
<Button x:Name="board" Clicked="board_Clicked" Text="board Setup" />
<Button x:Name="lblFinalPage" Clicked="lblFinalPage_Clicked" Text="FinalPage" />
Second page
<Picker SelectedItem="{Binding PICKER1}" WidthRequest="120" FontSize="10" TextColor="Black" VerticalOptions="Center" BackgroundColor="WhiteSmoke">
<Picker.Items>
<x:String>15 cm</x:String>
<x:String>30 cm</x:String>
<x:String>45 cm</x:String>
<x:String>60 cm</x:String>
<
</Picker.Items>
</Picker>
<Picker SelectedItem="{Binding PICKER2}" WidthRequest="120" FontSize="10" TextColor="Black" VerticalOptions="Center" BackgroundColor="WhiteSmoke">
<Picker.Items>
<x:String>Supine</x:String>
<x:String>Fst</x:String>
</Picker.Items>
</Picker>
third page
<Label Text="{Binding piCKER1}"></Label>
<Label Text="{Binding piCKER2}" ></Label>
secondview
public secondview()
{
SaveCommand = new Xamarin.Forms.Command(HandleAction);
}
async void HandleAction(object obj)
{
await ((App5.App)App.Current).MainPage.Navigation.PushAsync(
new finalPage()
{
BindingContext = new finalpageViewModel(PICKER1)
});
}
string picker1;
public string PICKER1
{
get
{
return picker1;
}
set
{
if (value != picker1)
{
picker1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PICKER1)));
}
}
}
public ICommand SaveCommand
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
}
finalpage
public finalpageViewModel(string pICKER1)
{
piCKER1 = pICKER1;
}
public string piCKER1
{
get;
set;
}
the issue I have with this here is, it only works if I have the button in the second page then I am able to pass the data to the final page,
but I would want the user to return to the mainpage and perform other stuff and click on the other button and navigate to the final page
You can create a DataModel model class to store the string data and it can be used to pass to the third page .
public class DataModel
{
public string PICKER1 { set; get; }
public string PICKER2 { set; get; }
public DataModel()
{
}
}
Then in MainPage , binding this data model and just passing MainPage's BindingContext to second page or third page .
public partial class PageMain : ContentPage
{
public DataModel dataModel;
public PageMain()
{
InitializeComponent();
dataModel = new DataModel();
BindingContext = dataModel;
}
private void Button_Clicked_Second(object sender, EventArgs e)
{
Navigation.PushAsync(new PageSecond(BindingContext));
}
private void Button_Clicked_Third(object sender, EventArgs e)
{
Navigation.PushAsync(new PageFinal(BindingContext));
}
}
SecondPage need to bind the passed BindingContext:
public partial class PageSecond : ContentPage
{
public PageSecond(object bindingContext)
{
InitializeComponent();
BindingContext = bindingContext;
}
}
ThirdPage also need to bind the passed BindingContext:
public partial class PageFinal : ContentPage
{
public PageFinal(object bindingContext)
{
InitializeComponent();
BindingContext = bindingContext;
}
}
The Xaml code of MainPage and SecondPage not need to be modified.However, the ThirdPage need to modify its binding name of Label text to keep the same with the DataModel:
<Label Text="{Binding PICKER1}"></Label>
<Label Text="{Binding PICKER2}" ></Label>
Now we can see the effect:

Caliburn Micro Conductor.Collection.AllActive not working

I have tried to activate multiple windows in application using Caliburn Micro with Conductor.Collection.AllActive
Steps Followed:
Inhertited MainHomeViewodel from Conductor.Collection.AllActive
1)created property
public ExploreViewModel Explorer {
get; private set;
}
2) Created ContentControl with name as property name
<ContentControl x:Name="Explorer" />
3)Activated viewmodel with property
Explorer = new ExplorerViewModel();
ActivateItem(Explorer );
After execution of above mentioned code its instantiating ExplorerViewModel but doesnt go to View's constructor or showing View .
Any problem with above implementation or do i need to do anything more to activate item.
Please Help!
Thanks.
EDIT
public class MainHomeWindowViewModel : Conductor<IScreen>.Collection.AllActive
{
protected override void OnInitialize()
{
base.OnInitialize();
ShowExplorer();
}
public void ShowExplorer()
{
Explorer = new ExplorerViewModel();
ActivateItem(Explorer );
}
}
Conductor.Collection.AllActive uses Items property. If you want to display multiple Screens at once, you have to add them to Items property.
Then, because your views are stored in Items property, you want to bind your view to Items. This is an example:
Conductor:
public class ShellViewModel : Conductor<IScreen>.Collection.AllActive
{
public ShellViewModel()
{
Items.Add(new ChildViewModel());
Items.Add(new ChildViewModel());
Items.Add(new ChildViewModel());
}
}
Conductor view (note, because we show collection of items we want to use ItemsSource not ContentControl):
<Grid>
<StackPanel>
<ItemsControl x:Name="Items"></ItemsControl>
</StackPanel>
</Grid>
Child screen:
public class ChildViewModel : Screen
{
}
Child view:
<Grid>
<Border Width="50" Height="50" BorderBrush="Red" BorderThickness="5"></Border>
</Grid>
EDIT: Regarding discussion in comments, here is how you can use IWindowManager to show multiple windows:
public class ShellViewModel : Screen
{
public ShellViewModel(IWindowManager windowManager)
{
var window1 = new ChildViewModel();
var window2 = new ChildViewModel();
windowManager.ShowWindow(window1);
windowManager.ShowWindow(window2);
window1.TryClose();
}
}

Displaying multiple screens in conductor doesn't work

I want to display a couple of screens at the same time. Displaying only one screen works fine, but when I switch my conductor to Conductor<DMChartBase>.Collection.AllActive and add another item, it still renders only one item.
public class DocumentViewModel : Conductor<DMChartBase>.Collection.AllActive
{
public ChartLegendViewModel ChartLegendVm { get; set; }
public DocumentViewModel()
{
ChartLegendVm = new ChartLegendViewModel();
ActivateItem(ChartLegendVm);
}
public void ChangeChart(DMChartBase chart, Column[] columns)
{
ActivateItem(chart);
Items.Last().Init(columns);
Items.Refresh();
}
}
And DocumentView:
<ItemsControl x:Name="Items"></ItemsControl>
I cannot find any reason why this doesn't work. Any ideas?
EDIT:
My code structure looks like this:
public class ShellViewModel : Screen
public class DocumentViewModel : Conductor<DMChartBase>.Collection.AllActive
public class ChartLegendViewModel : ChartDecorator
public abstract class ChartDecorator : DMChartBase
public abstract class DMChartBase : Screen
DocumentView:
<UserControl ...>
<Grid>
<ItemsControl x:Name="Items">
</Grid>
</UserControl>
ChartLegendView:
<UserControl ....>
<ListView>
<ListViewItem Content="First value"></ListViewItem>
<ListViewItem Content="Second value"></ListViewItem>
<ListViewItem Content="Third value"></ListViewItem>
</ListView>
</UserControl>
Bootstrapper:
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>()
}
EDIT:
I figured it out!
Previously I wanted to instantiate Chart and Legend separately which is wrong. DocumentViewModel should only be responsible for instantiating ChartDecorator. Inside ChartDecorator I can create as many decorator classes as I want (such as ChartLegendViewModel) and they all get drawn.
IoC isn't used at all? Secondly can you show us bootstrapper and XAML would help alittle. Are these items in a separate library? Normally Conductor uses a Screen or IScreen to have access to the "screens" lifetime. So unless DMChartBase inherits screen(IScreen) I don't think you will get an activation...
//MEF IoC Container -- CM Built-in
[Export(typeof(MainWindowViewModel))]
public class MainWindowViewModel : Conductor<IScreen>.Collection.OneActive
{
[ImportingConstructor]
public MainWindowViewModel([ImportMany]IEnumerable<IScreen> screens)
{
DisplayName = "COmposition Example - CM";
Items.AddRange(screens.Reverse());
}
protected override void OnActivate()
{
ActivateItem(Items[0]);
}
}
as an example... It shows all of them but only one is Active if you change it to AllActive they all would be active "can inter-communicate" but they aren't visible due to constraints of the control, used for this case (tab control) but if it was changed to an itemscontrols you still need to some work since the view wouldn't know how to deal with the binding of the view to the viewmodel

Listbox Trouble with Binding to ItemSource using a ObservableCollection

I am having trouble binding to the ItemsSource of a List box control. I would like to be able to add text lines to the List box when the user preforms certain actions.
The SystemControls.xmal Code:
<ListBox Grid.Column="4" Grid.Row="1" Grid.RowSpan="9" ItemsSource="{Binding ListBoxInput}" Height="165" HorizontalAlignment="Left" Name="listBox1" VerticalAlignment="Top" Width="250" ></ListBox>
The SystemControls.xmal.cs code snippet:
public partial class SystemControls : UserControl, ISystemControls
{
IDriver _Driver;
ISystemControls_VM _VM;
public SystemControls(IDriver InDriver, ISystemControls_VM InVM)
{
_VM = InVM;
_Driver = InDriver;
DataContext = new SystemControls_VM(_Driver);
InitializeComponent();
}
The SystemControls_VM.cs This should be where the heart of the problem is. I have gotten it to work in the constructor, when i try to add lines later in the code, for example when a user press a button, it does nothing:
public class SystemControls_VM:ViewModelBase, ISystemControls_VM
{
IDriver _Driver;
public ObservableCollection<string> _ListBoxInput = new ObservableCollection<string>();
public SystemControls_VM(IDriver InDriver)
{
_Driver = InDriver;
ListBoxInput.Add("test");//Works here
}
public ObservableCollection<string> ListBoxInput
{
get
{
return _ListBoxInput;
}
set
{
_ListBoxInput = value;
//OnPropertyChanged("ListBoxInput");
}
}
public void OnButtonClickGetNextError()
{
ListBoxInput.Add("NextErrorClicked");//Does not work here
}
public void OnButtonClickClear()
{
ListBoxInput.Clear();//Or Here
}
Also in case it's needed the OnPropertyChangedEventHandler:
namespace XXX.BaseClasses.BaseViewModels
{
/// <summary>
/// Provides common functionality for ViewModel classes
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate{};
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
1) Your public property is called _ListBoxInput but you're binding to ListBoxInput (no underscore). Make _ListBoxInput private.
2) Because the collection is already observable, you don't need the OnPropertyChanged for your listbox to update.
3) It looks like something might be off with the way you're managing your public vs private ListBoxInput collections. You're calling .Add on your public property (which will immediately raise an event on the observable collection) but then you'll end up adding it to the private collection as well, and then you're calling PropertyChanged on the public property. It's confusing: try my code below and see how it works. (Note in your constructor you add to _ListBoxInput but in your button click event you add to ListBoxInput.)
4) Try adding this.DataContext = this in your constructor
public partial class MainWindow : Window {
public ObservableCollection<string> ListBoxInput { get; private set; }
public MainWindow() {
InitializeComponent();
this.ListBoxInput = new ObservableCollection<string>();
this.DataContext = this;
}
private void AddListBoxEntry_Click(object sender, RoutedEventArgs e) {
this.ListBoxInput.Add("Hello " + DateTime.Now.ToString());
}
}
and in the xaml, take a look at the binding Mode.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding ListBoxInput, Mode=OneWay}"
Height="165" HorizontalAlignment="Left"
Name="listBox1" VerticalAlignment="Top" Width="250" />
<Button Grid.Column="1" Grid.Row="0" Name="AddListBoxEntry"
Margin="0,0,0,158" Click="AddListBoxEntry_Click" >
<TextBlock>Add</TextBlock>
</Button>
</Grid>
5) On a separate note, here's another way you could do your INotifyPropertyChanged (I find this cleaner)
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate{};
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
So got the answer from another source, but figured I would post it here for referance.
So what was happening was that I was setting the data context to one instance of SystemControls_VM while my _VM referance which was handling the button click was going to another instance of SystemControls_VM. That was also why it looked like the button click was working and the List was being populated but no data was getting to the Control itself
I changed the following section of code and it works:
public partial class SystemControls : UserControl, ISystemControls
{
IDriver _Driver;
SystemControls_VM _VM;
public SystemControls(IDriver InDriver, SystemControls_VM InVM)
{
_VM = InVM;
_Driver = InDriver;
DataContext = InVM;//new SystemControls_VM(_Driver);
InitializeComponent();
}

Dependency Property vs INotifyPropertyChanged in ViewModel for Windows 8 application

I have created blank C#/XAML Windows 8 application. Add simple XAML code:
<Page
x:Class="Blank.MainPage"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel
Margin="0,150"
HorizontalAlignment="Center">
<TextBlock
x:Name="xTitle"
Text="{Binding Title, Mode=TwoWay}"/>
<Button Content="Click me!" Click="OnClick" />
</StackPanel>
</Grid>
</Page>
And the simple code in C# part:
public sealed partial class MainPage
{
private readonly ViewModel m_viewModel;
public MainPage()
{
InitializeComponent();
m_viewModel = new ViewModel
{
Title = "Test1"
};
DataContext = m_viewModel;
}
private void OnClick(object sender, RoutedEventArgs e)
{
m_viewModel.Title = "Test2";
}
}
Now I want to implement ViewModel. I have two way:
Use Dependency Property
Implement INotifyPropertyChanged
For first approach it is:
public class ViewModel : DependencyObject
{
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string)
, typeof(ViewModel)
, new PropertyMetadata(string.Empty));
}
For second it is:
public class ViewModel : INotifyPropertyChanged
{
private string m_title;
public string Title
{
get
{
return m_title;
}
set
{
m_title = value;
OnPropertyChanged("Title");
}
}
protected void OnPropertyChanged(string name)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I prefer the first way, because it allows use coerce (Silverlight for web and for WP7 doesn't have coerce functionality.. WinRT too.. but I'm still looking and hope) and looks more natural for me. But unfortunately, it works as OneTime for the first approach.
Could anybody explain to me why MS abandon using Dependency Property for implementing view model?
You should not be using a DependencyProperty in your ViewModel - you should only use them in your controls. You will never want to bind one ViewModel to another, also ViewModels do not need to persist their values nor provide default values, nor provide property metadata.
You should only use INotifyPropertyChanged in your ViewModels.

Categories