How to update item xaml from another class in UWP - c#

Hi everyone i am a newbie in UWP development, i have searched a lot on the net but I have not found the right way to achieve my goal, what i would like to do is update the ui of my MainPage through the use of a class belonging to UiUpdate class. Here's what I'd like to get, this is my MainPage:
namespace Test_App
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
}
}
This is the relative xaml associated with my MainPage:
<Page
x:Class="Test_App.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test_App"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<controls:DockPanel>
<controls:DockPanel Visibility="Visible" HorizontalAlignment="Left" >
<Grid>
<TextBlock x:Name="text_one" HorizontalAlignment="Right" Margin="0,50,46,0" VerticalAlignment="Center" FontWeight="Bold" />
<TextBlock x:Name="text_two" HorizontalAlignment="Left" VerticalAlignment="Bottom" FontWeight="Medium" />
</Grid>
</controls:DockPanel>
</Grid>
</Page>
Now through the UiUpdate class, I would like to update my TextBlocks or any other element of my UI, I found something like this on the net:
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync (CoreDispatcherPriority.Normal, () =>
{
// Update texboxt
});
But i can't access in any way in the UiUpdate class the texbox elements of the MainPage i can't do MainPage.text_one .., also i can't find good documentation for what concerns patterns to be applied or other for the realization of my UWP app

How to update item xaml from another class in UWP
For explain this question, you may need to refer UWP mvvm design, and you mentioned UiUpdate most like ViewModel.
For example
<Page.DataContext>
<local:UiUpdate x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<TextBlock
x:Name="text_one"
Margin="0,50,46,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding TextBlockText}" />
<Button VerticalAlignment="Bottom" Click="Button_Click">Update</Button>
</Grid>
Code behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private Random random = new Random();
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel.TextBlockText = $"Text-----{random.Next(15)}";
}
}
public class UiUpdate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _textBlcokText;
public string TextBlockText
{
get
{
return _textBlcokText;
}
set
{
_textBlcokText = value;
OnPropertyChanged();
}
}
}

Related

Fail to Update the UI by using an ObservableCollection of MVVM bound objects

I would like to ask a question regarding the UI update of a WPF application based on changes applied to MVVM objects stored in a ObservableCollection. But first, let me explain my intuition.
I have the following files created to support my Project Solution. In total there are 5 files, so I present their code for you to replicate the issue. Copy-paste the code below in a new Solution project (WPF - .NET Core) and see for yourself my issue.
File 1: App.xaml
<Application x:Class="WpfAppTestingScenarios.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfAppTestingScenarios"
StartupUri="Window1.xaml">
<Application.Resources>
</Application.Resources>
</Application>
File 2: Window1.xaml
<Window x:Class="WpfAppTestingScenarios.Window1"
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:WpfAppTestingScenarios"
d:DataContext="{d:DesignInstance Type=local:LoginScreenViewModel}"
mc:Ignorable="d"
Title="Window1"
Height="450"
Width="800">
<Grid>
<Button Content="Click me"
Command="{Binding Path=LoginCommand}"
Height="20"
Width="110"/>
</Grid>
</Window>
File 3: MainWindow.xaml
<Window x:Class="WpfAppTestingScenarios.MainWindow"
x:Name="MainWindowName"
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:WpfAppTestingScenarios"
d:DataContext="{d:DesignInstance Type=local:MainWindowViewModel}"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition Width="640"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="225"/>
<RowDefinition Height="210"/>
</Grid.RowDefinitions>
<StackPanel
x:Name="StackPanel1"
Visibility="{Binding StackPanelVisibility1}"
Grid.Row="0"
Grid.Column="1">
<TextBlock Text="Hello World 1"/>
</StackPanel>
<Button
IsEnabled="{Binding Path=EnableViewButton1, UpdateSourceTrigger=PropertyChanged, FallbackValue=false}"
Content="View"
Width="80"
Height="25"
FontSize="10"
FontWeight="Light"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Grid.Row="0"
Grid.Column="1">
</Button>
<StackPanel
x:Name="StackPanel2"
Visibility="{Binding StackPanelVisibility2}"
Grid.Row="1"
Grid.Column="1">
<TextBlock Text="Hello World 2"/>
</StackPanel>
<Button
IsEnabled="{Binding Path=EnableViewButton2}"
Content="View"
Width="80"
Height="25"
FontSize="10"
FontWeight="Light"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Grid.Row="1"
Grid.Column="1">
</Button>
</Grid>
</Window>
File 4: Window1.xaml.cs
using Prism.Commands;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace WpfAppTestingScenarios
{
public class LoginScreenViewModel : INotifyPropertyChanged
{
public ICommand LoginCommand
{
get { return new DelegateCommand<object>(FuncLoginCommand); }
}
public void FuncLoginCommand(object parameters)
{
MainWindow WindMain = new MainWindow();
WindMain.Show();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new LoginScreenViewModel();
}
}
}
File 5: MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace WpfAppTestingScenarios
{
public class MyCustomClass : INotifyPropertyChanged
{
public string Key { get; set; }
private object _value;
public object Value
{
get { return _value; }
//set { _value = value; NotifyPropertyChanged($"{Value}"); }
//change to
set { _value = value; NotifyPropertyChanged(nameof(Value)); } //still no luck
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
//1. - StackPanelVisibility 1
private Visibility _StackPanelVisibility1;
public Visibility StackPanelVisibility1
{
get
{
return _StackPanelVisibility1;
}
set
{
_StackPanelVisibility1 = value;
OnPropertyChanged("StackPanelVisibility1");
}
}
//2. - StackPanelVisibility 2
private Visibility _StackPanelVisibility2;
public Visibility StackPanelVisibility2
{
get
{
return _StackPanelVisibility2;
}
set
{
_StackPanelVisibility2 = value;
OnPropertyChanged("StackPanelVisibility2");
}
}
//3. - EnableViewButoon 1
private bool _EnableViewButton1;
public bool EnableViewButton1
{
get
{
return _EnableViewButton1;
}
set
{
_EnableViewButton1 = value;
OnPropertyChanged("EnableViewButton1");
}
}
//4. - EnableViewButoon 2
private bool _EnableViewButton2;
public bool EnableViewButton2
{
get
{
return _EnableViewButton2;
}
set
{
_EnableViewButton2 = value;
OnPropertyChanged("EnableViewButton2");
}
}
private void CustomFunction(ObservableCollection<MyCustomClass> UICollection)
{
if ((Visibility)UICollection[0].Value == Visibility.Hidden)
UICollection[0].Value = Visibility.Visible;
if ((bool)UICollection[1].Value == false)
UICollection[1].Value = true;
}
public MainWindowViewModel()
{
ObservableCollection<MyCustomClass> dict = new ObservableCollection<MyCustomClass>
{
new MyCustomClass { Key = "StackPanelVisibility", Value = StackPanelVisibility1 },
new MyCustomClass { Key = "EnableViewButton", Value = EnableViewButton1 }
};
CustomFunction(dict);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
}
So even though everything was set successfully and with no errors, when I run the application and I click the button Click me the UI and thus the objects of the MainWindow are not updated.
Initially, I tried this logic with Dictionaries. But then I read that Dictionary cannot update the UI of a WPF application so I changed it to an ObservableCollection. However, both approaches didn't work for me.
Edit
Based on this answer, I created the following code
public class MyCustomClass : INotifyPropertyChanged
{
public string Key { get; set; }
private object _value;
public object Value
{
get { return _value; }
//set { _value = value; NotifyPropertyChanged($"{Value}"); }
//change to
set { _value = value; NotifyPropertyChanged(nameof(Value)); } //still no luck
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
But still, I don't observe any UI change.
My end result would be to enable the two buttons in the MainWindow like in the screen below (when I click the button Click me)
Image of my desired result

How to send an object to MainWindowViewModel using an child element view model inside a frame(WPF)?

I have a MainWindowViewModel and my MainWindow contains a frame to display project pages.
The first page being displayed is a list of recently opened projects(Similar to Microsoft word) which has it's own ViewModel.
There is no problem in loading the list but when I want to send the user-selected item from this list to the MainWindowViewModel I can not use Find-Ancestor to reach the Window DataContext(It looks like the frame has some restrictions).
How can I send the user-selected item to the MainWindowViewModel?
public class RecentlyOpenedFilesViewModel
{
readonly IFileHistoryService _fileHistoryService;
private ObservableCollection<RecentlyOpenedFileInfo> _RecentlyOpenedFilesList;
public ObservableCollection<RecentlyOpenedFileInfo> RecentlyOpenedFilesList
{
get { return _RecentlyOpenedFilesList; }
set { _RecentlyOpenedFilesList = value; RaisePropertyChanged(); }
}
public RecentlyOpenedFilesViewModel( IFileHistoryService fileService):base()
{
_fileHistoryService = fileService;
RecentlyOpenedFilesList=new ObservableCollection<RecentlyOpenedFileInfo>(_fileHistoryService.GetFileHistory());
}
public void RefreshList()
{
RecentlyOpenedFilesList = new ObservableCollection<RecentlyOpenedFileInfo>(_fileHistoryService.GetFileHistory());
}
}
<Page
x:Class="MyProject.Views.V3.Other.RecentlyOpenedFilesPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Views.V3.Other"
xmlns:vmv3="clr-namespace:MyProject"
Title="RecentlyOpenedFilesPage">
<Page.Resources>
<DataTemplate x:Key="RecentlyOpenedFileInfoTemplate"
>
<Button
Height="70"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.OpenProjectFromPathCommand}"
CommandParameter="{Binding}">
<Button.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Top">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Path}" />
</StackPanel>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="50,0,0,0"
VerticalAlignment="Center"
Text="{Binding DateModified}" />
</Grid>
</Button.Content>
</Button>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView
ItemTemplate="{StaticResource RecentlyOpenedFileInfoTemplate}"
ItemsSource="{Binding RecentlyOpenedFilesList}" />
</Grid>
public RecentlyOpenedFilesPage(MainWindowViewModel vm)
{
this.DataContext = vm;
InitializeComponent();
}
Now I have a direct link between MainWindowViewModel and RecentlyOpenedFilesViewModel but I would like to remove this dependency and use another way of connection like(routed commands which I have a problem with)
The MainWindow contains a frame in which the RecentlyOpenedFilesPage is set to its content.
<Window
x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fw="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF" >
<Frame Name="frameMain"/></Window>
public class MainWindowViewModel : RecentlyOpenedFilesViewModel, IMainWindowViewModel
{
private void LoadRecentlyOpenedProjects()
{
CurrentView = new RecentlyOpenedFilesPage(this);
}
}
So, here is my suggested solution. It uses the basic idea to propagate the DataContext from the outside into a frame content, as presented in page.DataContext not inherited from parent Frame?
For demonstration purpose, I provide an UI with a button to load the page, a textblock to display the selected result from the list within the page and (ofcourse) the frame that holds the page.
<Window x:Class="WpfApplication1.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Name="parentGrid">
<TextBlock VerticalAlignment="Top" HorizontalAlignment="Right" Margin="5" Text="{Binding SelectedFile}" Width="150" Background="Yellow"/>
<Button VerticalAlignment="Top" HorizontalAlignment="Left" Margin="5" Click="Button_Click" Width="150">Recent Files List</Button>
<Frame Name="frameMain" Margin="5 50 5 5"
LoadCompleted="frame_LoadCompleted"
DataContextChanged="frame_DataContextChanged"/>
</Grid>
</Window>
Viewmodel classes:
public class BaseVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class MyWindowVm : BaseVm
{
private string _selectedFile;
public string SelectedFile
{
get => _selectedFile;
set
{
_selectedFile = value;
OnPropertyChanged();
}
}
}
public class MyPageVm : BaseVm
{
public ObservableCollection<MyRecentFile> Files { get; } = new ObservableCollection<MyRecentFile>();
}
public class MyRecentFile
{
public string Filename { get; set; }
public string FilePath { get; set; }
}
Main code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
parentGrid.DataContext = new MyWindowVm();
}
// Load Page on some event
private void Button_Click(object sender, RoutedEventArgs e)
{
frameMain.Content = new RecentlyOpenedFilesPage(new MyPageVm
{
Files =
{
new MyRecentFile { Filename = "Test1.txt", FilePath = "FullPath/Test1.txt"},
new MyRecentFile { Filename = "Test2.txt", FilePath = "FullPath/Test2.txt"}
}
});
}
// DataContext to Frame Content propagation
private void frame_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
UpdateFrameDataContext(sender as Frame);
}
private void frame_LoadCompleted(object sender, NavigationEventArgs e)
{
UpdateFrameDataContext(sender as Frame);
}
private void UpdateFrameDataContext(Frame frame)
{
var content = frame.Content as FrameworkElement;
if (content == null)
return;
content.DataContext = frame.DataContext;
}
}
Now, the page.xaml ... notice: we will set the page viewmodel to the pageRoot.DataContext, not to the page itself. Instead we expect the page datacontext to be handled from the outside (as we do in the MainWindow) and we can reference it with the page internal name _self:
<Page x:Class="WpfApplication1.RecentlyOpenedFilesPage"
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="450" d:DesignWidth="800"
Title="RecentlyOpenedFilesPage"
Name="_self">
<Grid Name="pageRoot">
<ListView ItemsSource="{Binding Files}"
SelectedValue="{Binding DataContext.SelectedFile,ElementName=_self}"
SelectedValuePath="FilePath"
DisplayMemberPath="Filename"/>
</Grid>
</Page>
Page code behind to wire up the viewmodel:
public partial class RecentlyOpenedFilesPage : Page
{
public RecentlyOpenedFilesPage(MyPageVm myPageVm)
{
InitializeComponent();
pageRoot.DataContext = myPageVm;
}
}
As you can see, with this setup, no viewmodel knows about any involved view. The page doesn't handle the MainViewmodel, but the page requires a DataContext with a SelectedFile property to be provided from the outside.
The MainViewmodel doesn't know about the recent file list, but allows to set a selected file, no matter where it originates from.
The decision, to initialize the RecentlyOpenedFilesPage with a pre-created viewmodel is not important. You could just as well use internal logic to initialize the page with recent files, then the Mainwindow would not be involved.

Get Textblock in WPF to update using MVVM

So I just started playing around with WPF using MVVM and encountered a problem.
I want to create a simple program which takes a list of firstnames and surnames and puts together a random name. Everything works except that the name is not shown in the application window.
The button-command is correctly delegated to my function and the PropertyChanged Event is raised when I debug.
My code looks like this:
ViewModel:
class NamePicker: ObservableObject
{
private string _name;
public string Name
{
get{return _name;}
set
{
_name = value;
RaisePropertyChangedEvent("Name");
}
}
public ICommand CreateNameCommand
{
get { return new DelegateCommand(CreateName); }
}
public void CreateName()
{
Random rnd =new Random();
Name = randomNames.firstName[rnd.Next(0, randomNames.firstName.Length - 1)] + " "
+ randomNames.surName[rnd.Next(0, randomNames.surName.Length - 1)];
}
}
ObservableObject:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window
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:MVVM_Beispiel"
xmlns:ViewModels="clr-namespace:MVVM_Beispiel.ViewModels" x:Class="MVVM_Beispiel.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Command="{Binding CreateNameCommand}" Content="Name ausgeben" HorizontalAlignment="Left" Margin="202,292,0,0" VerticalAlignment="Top" Width="112">
<Button.DataContext>
<ViewModels:NamePicker/>
</Button.DataContext>
</Button>
<Label Content="Namensgenerator" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="202,10,0,0" Width="112"/>
<Image HorizontalAlignment="Left" Height="183" Margin="140,41,0,0" VerticalAlignment="Top" Width="244" Source="/MVVM-Beispiel;component/img/007.jpg"/>
<TextBlock HorizontalAlignment="Left" Margin="140,251,0,0" TextWrapping="Wrap" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="244">
<TextBlock.DataContext>
<ViewModels:NamePicker/>
</TextBlock.DataContext>
</TextBlock>
</Grid>
DelegateCommand:
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
}
So what exactly am I missing that the TextBlock doesn't show the current generated name? I read some tutorials but those don't go into detail about everything and it seems like I should find a good book about MVVM.
You have two different instances of NamePicker: first in Button and second one in TextBlock. So Name is changed only in first one. Remove all this stuff:
<Button.DataContext>
<ViewModels:NamePicker/>
</Button.DataContext>
<TextBlock.DataContext>
<ViewModels:NamePicker/>
</TextBlock.DataContext>
and set one view model to entire window:
<Window.DataContext>
<ViewModels:NamePicker/>
</Window.DataContext>

Exception Occurring Infinitely in Content Control

First, this is a simplified version from a wizard control using MVVM. The problem is just easier to reproduce as described below
After much narrowing down, I have resolved an infinite exception in my code to be due to the WPF ContentControl. However, I have yet to figure out how to handle it, other than try-catch wrapping all of my possible instantiation code. Here is sample code that reproduces this...any help on how to keep this infinite exception from occurring would be greatly appreciated.
Additional Details
To sum up, the problem is that if the content control changes its contents, and the thing being loaded in throws an exception, then it will throw, then retry the load, causing the throw again and again.
MainWindow.xaml
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Name ="Main">
<Grid>
<ContentControl Name="bar" Content="{Binding ElementName=Main, Path=foo}"/>
<Button Click="ButtonBase_OnClick" Margin="20" Width="50"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private UserControl _foo;
public UserControl foo
{
get { return _foo; }
set { _foo = value; OnPropertyChanged("foo"); }
}
public MainWindow()
{
InitializeComponent();
foo = new UserControl1();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
foo = new UserControl2();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
UserControl1 is blank and all default
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
throw new Exception();
}
Do not bind ContentControl to MainWindow. Instead use DataTemplates to select the content for the MainWindow. One example-contrived way of doing it is to bind the ContentControl's Content to the DataContext of the MainWindow.
First some observable test data is needed. The specifics of this data are not important. The main point is to have two different classes of test data from which to choose - TestData.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace fwWpfDataTemplate
{
// Classes to fill TestData
public abstract class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Student : Person { }
public class Employee : Person
{
float _salary;
public float Salary
{
get { return _salary; }
set
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
public class TestData : ObservableCollection<Person>
{
public TestData()
: base(new List<Person>()
{
new Student { Name = "Arnold" },
new Employee { Name = "Don", Salary = 100000.0f }
}) { }
}
}
Then add DataTemplates to MainWindow's resources - MainWindow.xaml:
<Window x:Class="fwWpfDataTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:fwWpfDataTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type me:Student}">
<StackPanel>
<TextBlock Text="Student"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type me:Employee}">
<StackPanel>
<TextBlock Text="Employee"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Salary"/>
<TextBlock Text="{Binding Salary}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Change Data Context" Click="Button_Click" />
<ContentControl Grid.Row="1" Content="{Binding}"/>
</Grid>
</Window>
Note: instead of the StackPanels the contents of the DataTemplates could be UserControl1, UserControl2, etc.
Then add some code to change the data context - MainWindow.cs:
using System.Windows;
namespace fwWpfDataTemplate
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
TestData testData = new TestData();
int testIndex = -1;
private void Button_Click(object sender, RoutedEventArgs e)
{
testIndex = (testIndex + 1) % testData.Count;
this.DataContext = testData[testIndex];
}
}
}
Enjoy.

Databind text to a string property

I'm new to Silverlight and the concept of data-binding and I still fail on resolving my problem. I haven't manage to find a solution after few days of research.
Here is my problem :
I correctly bind a String property to the text of my TextBlock as you can see below :
MainPage.xaml
<Grid Background="Blue" DataContext="{StaticResource WP8Displayable}">
<TextBlock x:Name="tbCanvasTitle" TextWrapping="Wrap" Text="{Binding titleDisplayable}" FontWeight="Bold" HorizontalAlignment="Center"/>
</Grid>
WP8Displayable.cs
public class WP8Displayable : IDisplayable, INotifyPropertyChanged
{
public String title { get; set; }
#region INotifyPropertyChanged Members
public string titleDisplayable
{
get
{
return title;
}
set
{
if (title != value)
{
title = value;
NotifyPropertyChanged("titleDisplayable");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public void setTitle(String s)
{
this.title = s;
NotifyPropertyChanged("titleDisplayable");
}
}
I have a thread in my MainPage.xaml.cs that can instantiate one or more instance of WP8Displayable class. When one of the instance call setTitle(String s) the text in my TextBlock does not update, it seems that my DataContext is not set up properly.
EDIT :
My thread is launched in the MainPage.xaml.cs in the MainPage_Loaded(object sender, RoutedEventArgs e) method and does something like this :
var instanceWP8Displayable = new WP8Displayable();
//tbCanvasTitle.DataContext = instanceWP8Displayable; HERE IS WHAT I WOULD LIKE TO DO ON XAML
instanceWP8Displayable.setTitle("my Title");
EDIT 2 : App.xaml
<Application
x:Class="AMS.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:windows="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:ioC="clr-namespace:AMS.Controller">
<!--Application Resources-->
<Application.Resources>
<windows:ResourceDictionary>
<local:LocalizedStrings xmlns:local="clr-namespace:AMS" x:Key="LocalizedStrings"/>
<ioC:Locator x:Key="Locator" x:Name="Locator" />
</windows:ResourceDictionary>
</Application.Resources>
MainPage.xaml
<Grid x:Name="LayoutRoot" Background="White">
<Grid Background="Blue" DataContext="{Binding Source={StaticResource Locator}, Path=WP8Displayable}">
<TextBlock x:Name="tbCanvasTitle" TextWrapping="Wrap" Text="{Binding titleDisplayable}" FontWeight="Bold" HorizontalAlignment="Center"/>
</Grid>
<Grid.DataContext>
<local:WP8Displayable />
</Grid.DataContext>
</Grid>
How can I dynamically set up the DataContext in that case ? And Is it possible to link more than one instance to the same object ?
If anyone as a clue or feels that my question is not clear enough don't hesitate to tell me.
Thank you.
I would recommend you check out MVVMLight which will help you eliminate a lot of boiler plate code you have to write e.g. INotifyPropertyChanged. Further it provides you with an IoC Container (Inversion of Control) that is most commonly used for the task you are trying to perform. You can implement a simple version of one on your own (see bellow).
You can set the DataContext in XAML but you will need a class that provides the Object, so for instance you could write a class like this (I'm assuming you add it directly to the project and not in a subfolder):
public class Locator
{
public WP8Displayable WP8Displayable
{
get { return new WP8Displayable(); }
}
}
Next you will have to register the Locator class in the App.xaml so you can reference it from within your view:
<Application
...
xmlns:windows="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:ioC="clr-namespace:YOURAPPNAME">
<!--Application Resources-->
<Application.Resources>
<windows:ResourceDictionary>
<local:LocalizedStrings xmlns:local="clr-namespace:AMS" x:Key="LocalizedStrings"/>
<ioC:Locator x:Key="Locator" x:Name="Locator" />
</windows:ResourceDictionary>
</Application.Resources>
...
</Application>
Now we can set the DataContext in XAML:
<Grid Background="Blue" DataContext="{Binding Source={StaticResource Locator}, Path=WP8Displayable}">
<TextBlock x:Name="tbCanvasTitle" TextWrapping="Wrap" Text="{Binding titleDisplayable}" FontWeight="Bold" HorizontalAlignment="Center"/>
</Grid>
HTH
Your way for declaration property is wrong.
Plz Declare property as below;
#region INotifyPropertyChanged Members
public string _titleDisplayable;
public string titleDisplayable
{
get
{
return _titleDisplayable;
}
set
{
if (_titleDisplayable != value)
{
_titleDisplayable = value;
NotifyPropertyChanged("titleDisplayable");
}
}
}
and In class methods plz write below code:
public void setTitle(string s)
{
this.titleDisplayable = s;
}
Plz changes ur code as per above code.
Thanks ,
Hitesh.

Categories

Resources