WPF how to access a Class Values from another form? - c#
Goal:
Using WPF I have created a Class called User.cs
here it is:
class User
{
public int id;
public int access;
public string username;
public int ID
{
get { return id; }
set { id = value; }
}
public int Access
{
get { return access; }
set { access = value; }
}
public string Username
{
get { return username; }
set { username = value; }
}
At my MainWindow.xaml.cs
I create a user and assign a value.
User u = new User();
private void Button_Click(object sender, RoutedEventArgs e)
{
u.id = 1;
u.access = 2;
u.username = "User1";
}
Question
From my new xaml form called Dashboard.xaml.cs. How can I access the information saved from MainWindow.xaml.cs ?
What I have tried
At Dashboard.xaml.cs
private void Window_Loaded(object sender, RoutedEventArgs e)
{
txt.Content = User.username
}
UPDATE: Using MVVM:
After some research and copying some examples here is where I got to.
Project Tree:
Ignore LoginScreen it is not used at all.
UserModel.cs
using System.ComponentModel;
namespace Technical_Application.Model
{
public class UserModel { }
public class User : INotifyPropertyChanged
{
private int id;
private int accessID;
private string username;
public int Id
{
get
{
return id;
}
set
{
if(id != value)
{
id = value;
RaisePropertyChanged("Id");
}
}
}
public int AccessID
{
get
{
return accessID;
}
set
{
if (accessID != value)
{
accessID = value;
RaisePropertyChanged("AccessID");
}
}
}
public string Username
{
get
{
return username;
}
set
{
if( username!= value)
{
username = value;
RaisePropertyChanged("Username");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
UserViewModel.cs
using Technical_Application.Model;
using System.Collections.ObjectModel;
namespace Technical_Application.ViewModel
{
public class UserViewModel
{
public ObservableCollection<User> Users
{
get;
set;
}
public void LoadUser()
{
ObservableCollection<User> users = new ObservableCollection<User>();
users.Add(new User { Id = 1});
users.Add(new User { AccessID = 1 });
users.Add(new User { Username = "User1" });
Users = users;
}
}
}
UserView.xaml
<UserControl x:Class="Technical_Application.Views.UserView"
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"
xmlns:local="clr-namespace:Technical_Application.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBlock Text = "{Binding Path = Username, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
**Dashboard.xaml**
<Window x:Class="Technical_Application.Dashboard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Technical_Application"
xmlns:views = "clr-namespace:Technical_Application.Views"
mc:Ignorable="d"
Title="Dashboard" Height="450" Width="800" Loaded="Window_Loaded">
<Grid>
<views:UserView x:Name="UserView" Loaded="UserView_Loaded"/>
</Grid>
</Window>
Dashboard.xaml.cs
namespace Technical_Application
{
/// <summary>
/// Interaction logic for Dashboard.xaml
/// </summary>
public partial class Dashboard : Window
{
public Dashboard()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
private void UserView_Loaded(object sender, RoutedEventArgs e)
{
Technical_Application.ViewModel.UserViewModel u =
new Technical_Application.ViewModel.UserViewModel();
u.LoadUser();
UserView.DataContext = u;
}
}
}
Next...
I need to figure out how to store information using a button click.
First of all let me recommend that you use MVVM pattern which will make your life easier now and in the future.
Now for your case, you can't directly access a random objects in C# (including forms) unless you have their reference.
the forms usually have a parent/child relationship which you can use to pass information between them. So if you do this in your MainWindow.xaml.cs:
Dashboard dash = new Dashboard(u);
dash.Show();
you can receive the user object in the constructor of the dashboard form. It is possible to act in the other direction and pass information from the child to the parent.
Related
Call a function from another ViewModel
I´m a beginner in this language, trying to learn more about the best practices and hows to do the things better... I have this repo as example application: https://github.com/Albvadi/NavigationMVVM If you run it, all works well. You can navigate to others views and increment a counter shared in all views. But, If you uncomment the ActualView assignation in MainViewModel.cs file at line 24 and put a login view in front of the InitialView I don´t know how to redirect the user to the Initialview after login success. When the login is correct, I fill the user data in the ManagerData and with all of this I need to call the function in the MainViewModel to redirect the user to the Initial view. How can I make that call from the LoginViewModel to the other MainViewModel instead from the View? UPDATE: Add the code relevant App.xaml <Application x:Class="NavigationMVVM.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:NavigationMVVM" xmlns:Views="clr-namespace:NavigationMVVM.Views" xmlns:ViewModels="clr-namespace:NavigationMVVM.ViewModels" StartupUri="MainWindow.xaml"> <Application.Resources> <DataTemplate DataType="{x:Type ViewModels:MainViewModel}"> <local:MainWindow /> </DataTemplate> <DataTemplate DataType="{x:Type ViewModels:LoginViewModel}"> <Views:Login /> </DataTemplate> <DataTemplate DataType="{x:Type ViewModels:InitialViewModel}"> <Views:Initial /> </DataTemplate> <DataTemplate DataType="{x:Type ViewModels:FirstViewModel}"> <Views:First /> </DataTemplate> <DataTemplate DataType="{x:Type ViewModels:SecondViewModel}"> <Views:Second /> </DataTemplate> </Application.Resources> </Application> MainWindow.xaml <Window x:Class="NavigationMVVM.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:NavigationMVVM" xmlns:viewModels="clr-namespace:NavigationMVVM.ViewModels" mc:Ignorable="d" WindowStartupLocation="CenterScreen" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <viewModels:MainViewModel /> </Window.DataContext> <Grid> <ContentControl Content="{Binding ActualView}" /> </Grid> </Window> BaseViewModel.cs using System.ComponentModel; namespace NavigationMVVM.Common { public class BaseViewModel : INotifyPropertyChanged { private BaseViewModel _ActualView; public BaseViewModel ActualView { get => _ActualView; set { _ActualView = value; RaisePropertyChanged(null); } } #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; public void RaisePropertyChanged(string PropertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName)); } #endregion } } MainViewModel.cs using NavigationMVVM.Common; using NavigationMVVM.Models; using System.Windows.Input; namespace NavigationMVVM.ViewModels { public class MainViewModel : BaseViewModel { public DataManager SharedData = new DataManager(); public InitialViewModel InitialVM; public FirstViewModel FirstVM; public SecondViewModel SecondVM; public LoginViewModel LoginVM; public MainViewModel() { LoginVM = new LoginViewModel(SharedData); InitialVM = new InitialViewModel(SharedData); FirstVM = new FirstViewModel(SharedData); SecondVM = new SecondViewModel(SharedData); ActualView = InitialVM; //ActualView = LoginVM; } public ICommand DisplayFirstView { get { return new RelayCommand(action => ActualView = FirstVM, canExecute => true); } } public ICommand DisplaySecondView { get { return new RelayCommand(action => ActualView = SecondVM, canExecute => true); } } public ICommand DisplayInitialView { get { return new RelayCommand(action => ActualView = InitialVM, canExecute => true); } } } } LoginViewModel.cs using NavigationMVVM.Common; using NavigationMVVM.Models; using System.Diagnostics; using System.Windows.Input; namespace NavigationMVVM.ViewModels { public class LoginViewModel : BaseViewModel { private DataManager _DataManager; public ICommand LoginCmd; public RelayCommand DoLoginCmd { get; } private string _Username; public string Username { get => _Username; set { _Username = value; RaisePropertyChanged(null); } } private string _Password; public string Password { get => _Password; set { _Password = value; RaisePropertyChanged(null); } } private string _MessageInfo; public string MessageInfo { get => _MessageInfo; set { _MessageInfo = value; RaisePropertyChanged(null); } } public LoginViewModel(DataManager sharedData) { _DataManager = sharedData; DoLoginCmd = new RelayCommand(param => DoLogin(), canExec => (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password))); } public void DoLogin() { if (Username == "admin" && Password == "password") { _DataManager.User.Name = "Administrator"; _DataManager.User.Mail = "admin#company.com"; MessageInfo = "Login OK!... How to redirect??"; } else { MessageInfo = "Username or Password incorrect!"; } } } } Thank you.
There are multiple ways to accomplish what you want. I'll post two approaches that are very common when working in an MVVM pattern Event based approach: public MainViewModel() { LoginVM = new LoginViewModel(SharedData); LoginVM.PropertyChanged += LoginVM_PropertyChanged; InitialVM = new InitialViewModel(SharedData); FirstVM = new FirstViewModel(SharedData); SecondVM = new SecondViewModel(SharedData); //ActualView = InitialVM; ActualView = LoginVM; } private void LoginVM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if(sender.GetType() == typeof(LoginViewModel) && e.PropertyName == "MessageInfo") { var loginVM = (LoginViewModel)sender; if (loginVM.MessageInfo == "OK") { ActualView = InitialVM; } } } Cunstructor Injection: private Action _loginAction; public LoginViewModel(DataManager sharedData, Action loginAction ) { _DataManager = sharedData; DoLoginCmd = new RelayCommand(param => DoLogin(), canExec => (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password))); _loginAction = loginAction; } public void DoLogin() { if (Username == "admin" && Password == "password") { _DataManager.User.Name = "Administrator"; _DataManager.User.Mail = "admin#company.com"; MessageInfo = "Login OK!... How to redirect??"; _loginAction.Invoke(); } else { MessageInfo = "Username or Password incorrect!"; } } and public MainViewModel() { LoginVM = new LoginViewModel(SharedData, () => ActualView = InitialVM); InitialVM = new InitialViewModel(SharedData); FirstVM = new FirstViewModel(SharedData); SecondVM = new SecondViewModel(SharedData); //ActualView = InitialVM; ActualView = LoginVM; } Side note: to put a property "ActualView" on the BaseViewModel is a rather odd choice
ComboBox items don't show up until the 1st column is sorted
The 2nd column items "Point Setting" don't show up until the 1st column items are sorted, clicking on the header of the 1st column. The goal of this code is to link the 1st and 2nd column items, then use the 2nd column items as the search keys. I'm new to C# and WPF. I tired to put sequential numbers in front of the 1st column items (1., 2., and so on) because I thought it would solve the problem if those items are initially sorted. But, no luck. I heard that ObservableCollection<> doesn't manage the input order, so once I changed it with List<>. But it didn't solve this problem, too. Actually, I don't want to sort the 1st column; they should be fixed and no need to change the order/number at all. To avoid any confusions, let me show my entire codes (sorry). MainWindow.xaml: <Window x:Class="XY.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:XY" mc:Ignorable="d" Title="MainWindow" Height="350" Width="454.4"> <Grid> <DataGrid ItemsSource="{Binding channels}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" Margin="0,0,0,-0.2"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding name}" Header="Channel" Width="Auto"/> <DataGridTemplateColumn Width="100" Header="Point Setting"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <ComboBox x:Name="piontsComboBox" ItemsSource="{Binding DataContext.points, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" SelectionChanged="PrintText" DisplayMemberPath="name" SelectedValuePath="name" Margin="5" SelectedItem="{Binding DataContext.SelectedPoint, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=TwoWay}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> <TextBox x:Name="tb" Width="140" Height="30" Margin="10,250,200,30"></TextBox> <Button x:Name="Browse_Button" Content="Browse" Margin="169,255,129.6,0" Width="75" Click="Browse_Button_Click" Height="30" VerticalAlignment="Top"/> </Grid> </Window> MainWindow.xaml.cs: using System; using System.Windows; using System.Windows.Controls; namespace XY { public partial class MainWindow : Window { public GridModel gridModel { get; set; } public MainWindow() { InitializeComponent(); gridModel = new GridModel(); this.DataContext = gridModel; } private void Browse_Button_Click(object sender, RoutedEventArgs e) { WakeupClass clsWakeup = new WakeupClass(); clsWakeup.BrowseFile += new EventHandler(gridModel.ExcelFileOpen); clsWakeup.Start(); } void PrintText(object sender, SelectionChangedEventArgs args) { var comboBox = sender as ComboBox; var selectedPoint = comboBox.SelectedItem as Point; tb.Text = selectedPoint.name; } } public class WakeupClass { public event EventHandler BrowseFile; public void Start() { BrowseFile(this, EventArgs.Empty); } } } ViewModelBase.cs: using System.ComponentModel; namespace XY { public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } } } Point.cs: namespace XY { public class Point : ViewModelBase { private string _name; public string name { get { return _name; } set { _name = value; OnPropertyChanged("name"); } } private int _code; public int code { get { return _code; } set { _code = value; OnPropertyChanged("code"); } } } } Record.cs: namespace XY { public class Record : ViewModelBase { private string _name; public string name { get { return _name; } set { _name = value; OnPropertyChanged("name"); } } private int _PointCode; public int PointCode { get { return _PointCode; } set { _PointCode = value; OnPropertyChanged("PointCode"); } } private Record _selectedRow; public Record selectedRow { get { return _selectedRow; } set { _selectedRow = value; OnPropertyChanged("SelectedRow"); } } private Point _selectedPoint; public Point SelectedPoint { get { return _selectedPoint; } set { _selectedPoint = value; _selectedRow.PointCode = _selectedPoint.code; OnPropertyChanged("SelectedRow"); } } } } GridModel.cs: using System.Collections.ObjectModel; using System.Windows; namespace XY { public class GridModel : ViewModelBase { public ObservableCollection<Record> channels { get; set; } public ObservableCollection<Point> points { get; set; } public GridModel() { channels = new ObservableCollection<Record>() { new Record {name = "1. High"}, new Record {name = "2. Middle"}, new Record {name = "3. Low"} }; } internal void ExcelFileOpen(object sender, System.EventArgs e) { points = new ObservableCollection<Point> { new Point { } }; MessageBox.Show("Please assume that Excel data are loaded here."); points.Add(new Point { name = "point1", code = 1 }); points.Add(new Point { name = "point2", code = 2 }); points.Add(new Point { name = "point3", code = 3 }); points.Add(new Point { name = "point4", code = 4 }); } } } The procedure goes like: Click on the "Browse" button to load the data. Click on the 1st column "Channel" to sort the list (GOAL: I'd like to GET RID OF this step). Click on the "Point Setting" ComboBox to select the items (point1, point2, ..., etc.). ... I don't know if ObservableCollection<> is appropriate here. If List<> or any other type is better, please change it. Any suggestion would be helpful. Thank you in advance.
Change your points ObservableCollection like such, because you're setting the reference of the collection after the UI is rendered, you would need to trigger the PropertyChanged event to update the UI. private ObservableCollection<Point> _points; public ObservableCollection<Point> points { get { return _points; } set { _points = value; OnPropertyChanged(nameof(points)); } } An alternative would be to first initialise your collection. public ObservableCollection<Point> points { get; set; } = new ObservableCollection<Point>(); internal void ExcelFileOpen(object sender, System.EventArgs e) { // Do not re-initialise the collection anymore. //points = new ObservableCollection<Point> { new Point { } }; points.Add(new Point { name = "point1", code = 1 }); points.Add(new Point { name = "point2", code = 2 }); points.Add(new Point { name = "point3", code = 3 }); }
Datagrid with ICollectionView SortDescription got lost - Bug?
here is what i want: if i bind a ICollectionview to a DataGrid, i dont wanna loose the SortDescription in my Viewmodel. i create a small sample project to see what i mean. In my projects i simply use a Usercontrol to show my data in a DataGrid. If i do this the SortDescritpion is gone when the UserControl Unload, because the ItemsSource is set to null. If i use a TemplateSelector to show my UserControl, the SortDescription is not gone and the ItemsSource ist not set to null on Unload. the question is, why are these different behaviors? Is one on the 2 behaviors a bug? btw. I use .Net 4.5.1 but 4.6.1 is installed and system.Windows.Interactivity 4.0.0.0 MainWindow.xaml <Window x:Class="DataGridICollectionView.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:DataGridICollectionView" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <DataTemplate DataType="{x:Type local:ViewmodelListe}"> <local:MyViewUc/> </DataTemplate> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ToolBar Grid.Row="0"> <Button Content="SetWorkspace MyView" Click="Button_Click"/> <Button Content="SetWorkspace Other" Click="Button_Click_1"/> </ToolBar> <ContentPresenter Grid.Row="1" Content="{Binding Workspace}"/> </Grid> </Window> MainWindow.xaml.cs namespace DataGridICollectionView { /// <summary> /// Interaktionslogik für MainWindow.xaml /// </summary> public partial class MainWindow : Window, INotifyPropertyChanged { private object _workspace; public MainWindow() { InitializeComponent(); MyViewVm = new ViewmodelListe(); DataContext = this; } public ViewmodelListe MyViewVm { get; set; } public object Workspace { get { return _workspace; } set { _workspace = value; OnPropertyChanged(); } } private void Button_Click(object sender, RoutedEventArgs e) { Workspace = MyViewVm; } private void Button_Click_1(object sender, RoutedEventArgs e) { Workspace = "Other"; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class ViewmodelListe : INotifyPropertyChanged { public ViewmodelListe() { Persons = new ObservableCollection<Person>(); MyView = CollectionViewSource.GetDefaultView(Persons); Persons.Add(new Person() {FirstName = "P1", LastName = "L1"}); Persons.Add(new Person() {FirstName = "P2", LastName = "L2"}); Persons.Add(new Person() {FirstName = "P3", LastName = "L3"}); } public ObservableCollection<Person> Persons { get; private set; } public ICollectionView MyView { get; private set; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class Person : INotifyPropertyChanged { private string _firstName; private string _lastName; public string FirstName { get { return _firstName; } set { _firstName = value; OnPropertyChanged(); } } public string LastName { get { return _lastName; } set { _lastName = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } public class TestBehavior : Behavior<DataGrid> { protected override void OnAttached() { base.OnAttached(); AssociatedObject.Unloaded += AssociatedObjectUnloaded; } private void AssociatedObjectUnloaded(object sender, RoutedEventArgs e) { //look at this in Debug Mode, its NULL if you dont use the TemplateSelector var itemssource = AssociatedObject.ItemsSource; } protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.Unloaded -= AssociatedObjectUnloaded; } } } MyGridControl.xaml <UserControl x:Class="DataGridICollectionView.MyGridControl" 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" xmlns:local="clr-namespace:DataGridICollectionView" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <DataGrid ItemsSource="{Binding MyView}" AutoGenerateColumns="True"> <i:Interaction.Behaviors> <local:TestBehavior/> </i:Interaction.Behaviors> </DataGrid> </Grid> </UserControl> MyViewUc.xaml <UserControl x:Class="DataGridICollectionView.MyViewUc" 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" xmlns:local="clr-namespace:DataGridICollectionView" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <UserControl.Resources> <DataTemplate x:Key="MyViewCrap"> <local:MyGridControl/> </DataTemplate> <local:MyTemplateSelector x:Key="Selector" GridView="{StaticResource MyViewCrap}" /> </UserControl.Resources> <Grid> <!--When using Contentcontrol with TemplateSelector- ItemsSource is NOT set to null --> <ContentControl Content="{Binding .}" ContentTemplateSelector="{StaticResource Selector}"/> <!--When using MyGridControl withOUT TemplateSelector- ItemsSource is set to NULL --> <!--<local:MyGridControl/>--> </Grid> </UserControl> MyViewUc.xaml.cs namespace DataGridICollectionView { /// <summary> /// Interaktionslogik für MyViewUc.xaml /// </summary> public partial class MyViewUc : UserControl { public MyViewUc() { InitializeComponent(); } } public class MyTemplateSelector : DataTemplateSelector { public DataTemplate GridView { get; set; } public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) { var chooser = item as ViewmodelListe; if (chooser == null) { return base.SelectTemplate(item, container); } return GridView; } } } EDIT: i end up using this public class MyDataGrid : DataGrid { static MyDataGrid () { ItemsSourceProperty.OverrideMetadata(typeof(MyDataGrid ),new FrameworkPropertyMetadata(null, OnPropertyChangedCallBack, OnCoerceItemsSourceProperty)); } private ICollectionView _defaultView; protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { if(_defaultView != null) _defaultView.CollectionChanged -= LiveSortingPropertiesOnCollectionChanged; base.OnItemsSourceChanged(oldValue, newValue); _defaultView = newValue as ICollectionView; if(_defaultView != null) _defaultView.CollectionChanged += LiveSortingPropertiesOnCollectionChanged; } private void LiveSortingPropertiesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Reset) { foreach (var dataGridColumn in this.Columns) { var isSortDirectionSetFromCollectionView = false; foreach (var sortDescription in _defaultView.SortDescriptions) { if (dataGridColumn.SortMemberPath == sortDescription.PropertyName) { dataGridColumn.SortDirection = sortDescription.Direction; isSortDirectionSetFromCollectionView = true; break; } } if (!isSortDirectionSetFromCollectionView) { dataGridColumn.SortDirection = null; } } } } private static void OnPropertyChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e) { var grd = d as MyDataGrid ; var view = e.NewValue as ICollectionView; if (grd == null || view == null) return; foreach (var dataGridColumn in grd.Columns) { var isSortDirectionSetFromCollectionView = false; foreach (var sortDescription in view.SortDescriptions) { if (dataGridColumn.SortMemberPath == sortDescription.PropertyName) { dataGridColumn.SortDirection = sortDescription.Direction; isSortDirectionSetFromCollectionView = true; break; } } //wenn die View nicht sortiert war, auch die column nicht Sortieren if (!isSortDirectionSetFromCollectionView) { dataGridColumn.SortDirection = null; } } } private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue) { // do nothing here - we just want to override parent behaviour. // The _only_ thing DataGrid does here is clearing sort descriptors return baseValue; } }
When you host your MyGridControl directly inside MyViewUc (case 1) - when you switch workspace and MyViewUC is unloaded, it's datacontext is set to null. Because MyGridControl is direct child - it's datacontext is set to null too, and in turn DataContext of DataGrid. This sets ItemsSource to null too, because it's bound to DataContext. You can verify this by looking at DataContext of DataGrid in your behavior. This behavior is completely reasonable to my mind. When you use template selector: MyViewUC is unloaded, it's datacontext is set to null. Then ContentControl Content is set to null, too. Now here is the problem: when you use ContentTemplateSelector, DataContext of your old (unloaded) MyGridControl is NOT set to null. You can verify this in your behaviour, that is why ItemsSource and sort descriptors are preserved. Now, I believe this second behaviour is not correct and datacontext should be set to null for this unloaded control created by ContentTemplateSelector. The logic behind this is not very simple - you can look yourself at source code of ContentPresenter.OnContentChanged method, where you will see when DataContext is not updated when content changes. UPDATE: I see your main concern is losing sort descriptors, but that is direct consequence of losing DataContext and setting ItemsSource to null. To me this behaviour looks reasonable, but I see indeed for many people it is not, so that there is even bug report about this issue: https://connect.microsoft.com/VisualStudio/feedback/details/592897/collectionviewsource-sorting-only-the-first-time-it-is-bound-to-a-source You can see yourself in DataGrid source code that: protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { base.OnItemsSourceChanged(oldValue, newValue); if (newValue == null) this.ClearSortDescriptionsOnItemsSourceChange(); // more code here.... } So when you set ItemsSource to null - all sort descriptors are explicitly cleared. At link above you can find some workarounds which you might find useful. UPDATE2: you can consider trying to fix that behaviour by inheriting from DataGrid. I don't say that is perfect solution, but neither is using ContentTemplateSelector. There are two places where sort descriptors are cleared when ItemsSource is set to null - in OnItemsSourceChanged and OnCoerceItemsSourceProperty. So you can do the following: public class MyDataGrid : DataGrid { static MyDataGrid() { ItemsSourceProperty.OverrideMetadata(typeof(MyDataGrid), new FrameworkPropertyMetadata(null, OnCoerceItemsSourceProperty)); } private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue) { // do nothing here - we just want to override parent behaviour. // The _only_ thing DataGrid does here is clearing sort descriptors return baseValue; } protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { SortDescription[] sorts = null; if (newValue == null) { // preserve sort descriptors when setting ItemsSource to null sorts = Items.SortDescriptions.ToArray(); } // they will now be cleared here base.OnItemsSourceChanged(oldValue, newValue); if (sorts != null) { // restore them back foreach (var sort in sorts) { Items.SortDescriptions.Add(sort); } } } } With code above you will see that sort descriptors are preserved in your MyView between switching datacontext.
Metro UI not update
I'm just start working on metro app and i'm facing a problem that is dispatcher not updating the UI. My code is below please let me know what was the issue ? public class Test : DependencyObject { public static readonly DependencyProperty CurrentItemProperty = DependencyProperty.Register("NameOfPerson", typeof(string), typeof(Test), null); public String NameOfPerson { get { return (string)GetValue(CurrentItemProperty); } set { runmethod(value); } } public async void runmethod(String text) { await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { SetValue(CurrentItemProperty, text); } ); } } In main page i have an event button click which when fire update textbox. private void Button_Click_2(object sender, RoutedEventArgs e) { Test t = new Test(); t.NameOfPerson = "Hello Umar"; } MainPage.xaml look like this <Page x:Class="TestApplication.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:TestApplication" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Button Content="Button" HorizontalAlignment="Left" Margin="207,187,0,0" VerticalAlignment="Top" Height="80" Width="255" Click="Button_Click_2"/> <TextBox x:Name="textB" Text="{Binding NameOfPerson}" HorizontalAlignment="Left" Height="80" Margin="730,187,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="307"/> </Grid> </Page>
If you are what you are trying to do is having a button refresh your Text, you should look into the MVVM pattern and have the Binding update your UI. To do this you'll have to create your Object, in this case, let's say a person. public class Person { public string Name { get; set; } } Secondly you would want to have a person inside a viewmodel that you'll update using your button. The viewmodel will derive from BindableBase which is a part of Windows Store applications if you would use such thing as Basic Page. The Viewmodel looks like this: public class MainPageViewModel : BindableBase { public MainPageViewModel() { } private Person person; public Person Person { get { return person; } set { SetProperty(ref person, value); } } public void LoadData() { Person = new Person() { Name = "Simple name" }; } public void UpdatePerson() { Person.Name = "Updated Name"; OnPropertyChanged("Person"); } } and in case you dont have the bindableBase, it looks like this: [Windows.Foundation.Metadata.WebHostHidden] public abstract class BindableBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null) { if (object.Equals(storage, value)) return false; storage = value; this.OnPropertyChanged(propertyName); return true; } protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { var eventHandler = this.PropertyChanged; if (eventHandler != null) { eventHandler(this, new PropertyChangedEventArgs(propertyName)); } } } On your MainPage you create the ViewModel and set the DataContext on your Page. Also you would want to handle your object inside your Viewmodel, so you'll create a update method when clicking the button that will modify your Person object: public sealed partial class MainPage : Page { private readonly MainPageViewModel viewModel; public MainPage() { this.InitializeComponent(); viewModel = new MainPageViewModel(); viewModel.LoadData(); this.DataContext = viewModel; } private void Button_Tapped(object sender, TappedRoutedEventArgs e) { viewModel.UpdatePerson(); } } And finally your TextBox in the UI to point at the Person's name property inside the Viewmodel: <TextBox x:Name="textB" Text="{Binding Person.Name}" HorizontalAlignment="Left" Height="80" Margin="730,187,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="307" /> I hope this fulfills your question on how you can have a button updating your UI.
Unable to set property in usercontrol from viewmodel
I am trying to set a property in a usercontrol from a view model, the control is meant to capture and display the users signature. I am having problems setting the property in the usercontrol from a view model. Receiving the signature from the usercontrol into the view model works fine. Any ideas on how to solve this would be of greatly appreciated. My test code is below. User control XAML: <UserControl x:Class="mvvmSignature.ucSignature" 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="212" d:DesignWidth="300" Background="#FFEFFCEF"> <Grid Height="210"> <InkCanvas MinHeight="73" HorizontalAlignment="Left" Margin="10,27,0,0" Name="inkCanvas1" VerticalAlignment="Top" MinWidth="278" LostMouseCapture="inkCanvas1_LostMouseCapture" /> <Button Content="Clear" Height="23" HorizontalAlignment="Left" Margin="33,142,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="buttonClear_Click" /> <Button Content="Load on user control" Height="23" Margin="143,143,36,45" Click="ButtonLoad_Click" /> </Grid> </UserControl> UserControl CodeBehind: using System.IO; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Ink; using System.Windows.Input; namespace mvvmSignature { public partial class ucSignature : UserControl { public ucSignature() { InitializeComponent(); } private void inkCanvas1_LostMouseCapture(object sender, MouseEventArgs e) { SetSignature(); } private void SetSignature() { var sb = new StringBuilder(); using (var ms = new MemoryStream()) { inkCanvas1.Strokes.Save(ms); foreach (var item in ms.ToArray()) { sb.AppendFormat("{0},", item); } ms.Close(); } var local = sb.ToString().Trim() + "¬¬¬"; local = local.Replace(",¬¬¬", string.Empty); Signature = local; } private void LoadSignature(string signatureIn) { if (string.IsNullOrEmpty(signatureIn) || !signatureIn.Contains(",")) return; var x = signatureIn.Split(',').Select(byte.Parse).ToArray(); if (!x.Any()) return; using (var ms = new MemoryStream(x)) { inkCanvas1.Strokes = new StrokeCollection(ms); ms.Close(); } } public static DependencyProperty SignatureProperty = DependencyProperty.Register("Signature", typeof(string), typeof(ucSignature)); public string Signature { get { return (string) GetValue(SignatureProperty); } set { SetValue(SignatureProperty, value); LoadSignature(value); } } private void ButtonLoad_Click(object sender, RoutedEventArgs e) { Signature = "0,136,3,3,6,72,16,69,53,70,53,17,0,0,128,63,31,9,17,0,0,0,0,0,0,240,63,10,237,2,186,1,135,240,12,39,128,97,109,28,4,156,51,90,235,138,135,131,227,95,107,253,119,193,35,104,195,186,246,103,1,195,179,59,78,134,195,179,92,167,188,180,76,206,211,192,77,175,108,211,28,53,136,32,51,41,156,210,3,148,109,22,188,163,105,195,188,11,92,11,184,122,101,188,166,182,124,53,106,153,90,44,217,71,15,64,102,115,59,52,205,105,1,53,128,76,166,179,73,156,202,107,0,195,248,122,106,153,64,22,185,164,210,3,51,154,64,102,51,8,5,174,105,52,128,204,102,19,57,156,215,41,90,69,162,103,1,128,76,128,19,40,4,6,1,102,153,205,96,22,105,156,206,101,53,180,89,230,83,88,4,202,107,0,179,38,120,122,215,52,195,248,120,179,101,25,158,80,195,179,41,156,210,107,50,180,205,38,144,25,156,214,105,52,153,97,249,165,166,101,52,180,205,32,48,0,135,231,164,231,113,153,131,40,225,168,4,6,1,1,128,89,160,22,105,164,214,207,135,32,56,110,3,52,153,192,96,19,80,0,11,68,6,1,105,11,76,2,215,135,50,142,28,77,48,253,160,180,225,232,12,2,107,52,128,205,38,115,91,68,214,105,51,153,77,112,238,30,77,102,80,9,148,214,1,1,154,76,230,64,38,80,8,12,2,101,50,180,205,11,70,29,128,225,236,63,0,153,77,96,19,9,141,154,207,102,153,204,230,19,24,12,209,105,154,64,96,19,89,148,204,0,76,166,118,121,132,204,77,38,179,56,13,170,3,51,128,64,109,24,126,104,154,32,54,121,148,202,103,52,153,77,96,9,144"; } private void buttonClear_Click(object sender, RoutedEventArgs e) { inkCanvas1.Strokes.Clear(); SetSignature(); } } } MainWindow XAML: <Window xmlns:my="clr-namespace:mvvmSignature" x:Class="mvvmSignature.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ignore="http://www.ignore.com" mc:Ignorable="d ignore" Height="490" Width="327" DataContext="{Binding Main, Source={StaticResource Locator}}"> <StackPanel> <my:ucSignature Signature ="{Binding Signature,Mode=TwoWay}" /> <Button Width ="140" Height ="30" Content ="Load On Main Page" Command="{Binding LoadOnMainPageCommand}" /> <TextBox FontSize="8" Foreground="Purple" Text="{Binding Signature,Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="Wrap" Height="210" /> </StackPanel> MainViewModel: using System.Windows.Input; using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; namespace mvvmSignature.ViewModel { public class MainViewModel : ViewModelBase { public ICommand LoadOnMainPageCommand { get; set; } public const string WelcomeTitlePropertyName = "Signature"; private string _signature = string.Empty; public string Signature { get { return _signature; } set { if (_signature == value) { return; } _signature = value; RaisePropertyChanged(WelcomeTitlePropertyName); } } public MainViewModel() { LoadOnMainPageCommand = new RelayCommand(LoadSignature); } private void LoadSignature() { Signature ="0,136,3,3,6,72,16,69,53,70,53,17,0,0,128,63,31,9,17,0,0,0,0,0,0,240,63,10,237,2,186,1,135,240,12,39,128,97,109,28,4,156,51,90,235,138,135,131,227,95,107,253,119,193,35,104,195,186,246,103,1,195,179,59,78,134,195,179,92,167,188,180,76,206,211,192,77,175,108,211,28,53,136,32,51,41,156,210,3,148,109,22,188,163,105,195,188,11,92,11,184,122,101,188,166,182,124,53,106,153,90,44,217,71,15,64,102,115,59,52,205,105,1,53,128,76,166,179,73,156,202,107,0,195,248,122,106,153,64,22,185,164,210,3,51,154,64,102,51,8,5,174,105,52,128,204,102,19,57,156,215,41,90,69,162,103,1,128,76,128,19,40,4,6,1,102,153,205,96,22,105,156,206,101,53,180,89,230,83,88,4,202,107,0,179,38,120,122,215,52,195,248,120,179,101,25,158,80,195,179,41,156,210,107,50,180,205,38,144,25,156,214,105,52,153,97,249,165,166,101,52,180,205,32,48,0,135,231,164,231,113,153,131,40,225,168,4,6,1,1,128,89,160,22,105,164,214,207,135,32,56,110,3,52,153,192,96,19,80,0,11,68,6,1,105,11,76,2,215,135,50,142,28,77,48,253,160,180,225,232,12,2,107,52,128,205,38,115,91,68,214,105,51,153,77,112,238,30,77,102,80,9,148,214,1,1,154,76,230,64,38,80,8,12,2,101,50,180,205,11,70,29,128,225,236,63,0,153,77,96,19,9,141,154,207,102,153,204,230,19,24,12,209,105,154,64,96,19,89,148,204,0,76,166,118,121,132,204,77,38,179,56,13,170,3,51,128,64,109,24,126,104,154,32,54,121,148,202,103,52,153,77,96,9,144"; } } }
I re-wrote portions of your ucSignature class to properly declare and use the Signature property in your code behind. Use this class definition instead, and let me know if it works any better. public partial class ucSignature : UserControl { public ucSignature() { InitializeComponent(); } private void inkCanvas1_LostMouseCapture(object sender, MouseEventArgs e) { var sb = new StringBuilder(); using (var ms = new MemoryStream()) { inkCanvas1.Strokes.Save(ms); foreach (var item in ms.ToArray()) { sb.AppendFormat("{0},", item); } ms.Close(); } var local = sb.ToString().Trim() + "¬¬¬"; local = local.Replace(",¬¬¬", string.Empty); this.SetValue(SignatureProperty, local); } private void LoadSignature(string signatureIn) { if (string.IsNullOrEmpty(signatureIn) || !signatureIn.Contains(",")) return; var x = signatureIn.Split(',').Select(byte.Parse).ToArray(); if (!x.Any()) return; using (var ms = new MemoryStream(x)) { inkCanvas1.Strokes = new StrokeCollection(ms); ms.Close(); } } public static DependencyProperty SignatureProperty = DependencyProperty.Register( "Signature", typeof(string), typeof(ucSignature), new UIPropertyMetadata(OnSignaturePropertyChanged)); public static string GetSignature(ucSignature signature) { return (string)signature.GetValue(SignatureProperty); } public static void SetSignature(ucSignature signature, string value) { signature.SetValue(SignatureProperty, value); } private static void OnSignaturePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { var signature = obj as ucSignature; if (signature != null) { LoadSignature(args.NewValue.ToString()); } } private void ButtonLoad_Click(object sender, RoutedEventArgs e) { this.SetValue(SignatureProperty, "0,136,3,3,6,72,16,69,53,70,53,17,0,0,128,63,31,9,17,0,0,0,0,0,0,240,63,10,237,2,186,1,135,240,12,39,128,97,109,28,4,156,51,90,235,138,135,131,227,95,107,253,119,193,35,104,195,186,246,103,1,195,179,59,78,134,195,179,92,167,188,180,76,206,211,192,77,175,108,211,28,53,136,32,51,41,156,210,3,148,109,22,188,163,105,195,188,11,92,11,184,122,101,188,166,182,124,53,106,153,90,44,217,71,15,64,102,115,59,52,205,105,1,53,128,76,166,179,73,156,202,107,0,195,248,122,106,153,64,22,185,164,210,3,51,154,64,102,51,8,5,174,105,52,128,204,102,19,57,156,215,41,90,69,162,103,1,128,76,128,19,40,4,6,1,102,153,205,96,22,105,156,206,101,53,180,89,230,83,88,4,202,107,0,179,38,120,122,215,52,195,248,120,179,101,25,158,80,195,179,41,156,210,107,50,180,205,38,144,25,156,214,105,52,153,97,249,165,166,101,52,180,205,32,48,0,135,231,164,231,113,153,131,40,225,168,4,6,1,1,128,89,160,22,105,164,214,207,135,32,56,110,3,52,153,192,96,19,80,0,11,68,6,1,105,11,76,2,215,135,50,142,28,77,48,253,160,180,225,232,12,2,107,52,128,205,38,115,91,68,214,105,51,153,77,112,238,30,77,102,80,9,148,214,1,1,154,76,230,64,38,80,8,12,2,101,50,180,205,11,70,29,128,225,236,63,0,153,77,96,19,9,141,154,207,102,153,204,230,19,24,12,209,105,154,64,96,19,89,148,204,0,76,166,118,121,132,204,77,38,179,56,13,170,3,51,128,64,109,24,126,104,154,32,54,121,148,202,103,52,153,77,96,9,144"); } private void buttonClear_Click(object sender, RoutedEventArgs e) { inkCanvas1.Strokes.Clear(); this.SetValue(SignatureProperty, string.Empty); } }
You need to set your MainViewModel as the DataContext of your MainWindow so the binding works. Unless you're setting it in the constructor, I don't see it being set anywhere. You can try something like this: <Application.Resources> <local:MainViewModel x:Key="MainViewModel" /> </Application.Resources> DataContext="{StaticResource MainViewModel}"
I'm going to assume that it's the Signature property that you are trying to set, mainly because that's what your user control is all about AND that's where I see an issue. After you set the Signature property, you call RaisePropertyChanged on a different property: the WelcomeTitlePropertyName property. You should be calling RaisePropertyChanged on the same property you set or else your UI (i.e. your user control) won't know about it. For example, in your MainWindow XAML you placed a ucSignature element like this: <my:ucSignature Signature ="{Binding Signature,Mode=TwoWay}" /> That part is fine, but in your ViewModel that property should be defined like this: private string _signature = string.Empty; public string Signature { get { return _signature; } set { if (_signature == value) return; _signature = value; RaisePropertyChanged(Signature); } }