I'm pretty new to WPF and i'm trying to load a XAML window and pass a variable to this XAML in its constructor or so, because i need it to load some items from this passed variable.
Could anyone point me to the direction of how to go about this please?
How does one start up a XAML window and give it a variable please?
Thanks in advance..
Erika
Try to use MVVM (Model-View-ViewModel) pattern.
You need Model:
class Person
{
public string Name { get; set; }
}
View is your window or UserControl.
ViewModel can be something like that:
class PersonViewModel : INotifyPropertyChanged
{
private Person Model;
public PersonViewModel(Person model)
{
this.Model = model;
}
public string Name
{
get { return Model.Name; }
set
{
Model.Name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChangedEventHandler changed = PropertyChanged;
if (changed != null) changed(this, e);
}
}
Then you need to specify DataContext for your window:
View.DataContext = new PersonViewModel(somePerson);
And then define bindings in XAML:
<UserControl x:Class="SomeApp.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<TextBlock Text="{Binding Name}" />
<Grid>
<UserControl>
MVVM makes code very elegant and easy.
You can also try PRISM or Caliburn (http://caliburn.codeplex.com/) frameworks but they are more complex.
Typically, in WPF, you'd create the items you want to load, and set the Window (or UserControl)'s DataContext to the class that contains your items. You can then bind directly to these in order to do custom display from the XAML.
My issue was that i wanted to access a class outside of the XAML window, not communicate with the relative code via Binding. For this, all i needed to do was create a static class to hold the value i required. Simple, yet the solution escaped me at that point. Its a rather dirty way to go round solving the problem but it does the trick.
I would like to thank both contributors who helped me understand the MVVM architecture as I hadnt really understood that previously.
Thanks so much for the prompt reply and sorry if my question was easily missunderstood! Sometimes im not very good at conveying my ideas..
If you want to see a bit more on MVVM then check out this (MVVM is really powerful and a great way to develop testable code):
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
It should give you a very good idea of MVVM. There is an example you can download just under the heading where it says: Code download available from the MSDN Code Gallery.
Related
In my code, I have an UIElement variable I set with certain button presses.
Now, I have this variable:
public UIElement currentMenu;
Set to this value:
currentMenu = (UIElement)Resources["Home"];
I get it from the Resources so I don't have to manage it messily in the code-behind, I will export the Resources to a seperate ResourceDictionary once I get this problem solved.
My SplitView looks like this:
<SplitView x:Name="NavPane" OpenPaneLength="250" CompactPaneLength="50" Content="{x:Bind currentMenu}" DisplayMode="CompactOverlay" IsPaneOpen="False" PaneClosing="NavPane_PaneClosing">
The prblem comes in at this point, the Binding crashes the entire application with an unhndled win32 exception. I get no description and the error code changes every time. I have checked with break points whether this behaviour is actually caused by the binding, and it is.
If you have any suggestions on what might be going wrong here, please post an answer. I will supply any additional information needed (if reasonable, I'm not going to send you my entire project files)
Any help is appreciated!
Your problem that you are using a variable, not a property.
private UIElement currentMenu;
public string CurrentMenu
{
get { return currentMenu; }
set {
currentMenu=value);
OnPropertyChanged("CurrentMenu");
}
}
So the basic rules to bind Control to a "varaible":
Variable should be a property, not a field.
it should be public.
Either a notifying property (suitable for model classes) or a dependency property (suitable for view classes)
To notify UI you should implement INotifyPropertyChanged:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Update:
Your Bidning should looks like:
<SplitView x:Name="NavPane" OpenPaneLength="250" CompactPaneLength="50"
Content="{Binding CurrentMenu}" DisplayMode="CompactOverlay" IsPaneOpen="False"
PaneClosing="NavPane_PaneClosing">
I have found the answer to my question!
Namely, this was not the way to do it. Instead, I declared a Frame inside the Content of the SplitView:
<SplitView.Content>
<Frame x:Name="activeMenu"/>
</SplitView.Content>
Then, I use the Frame.Navigate() function to load my menus into the Frame:
public MainPage()
{
DataContext = this;
this.InitializeComponent();
SetMenu(0);
}
private void SetMenu(int key)
{
switch (key)
{
case 0:
activeMenu.Navigate(typeof(HomeMenu));
break;
//You can add in as many cases as you need
}
}
What you then need is to set all the menus you want as separate Pages within your project files. in this example, HomeMenu.xaml contains the grid for the menu people see upon starting up the app.
This solved the problem, but thank you to everyone (StepUp) who contributed to the original (unfortunately unsuccessful) solution!
The question is very simple, yet every time I see similar questions answered here, the answers don't explain a way to do it with a simple example. Here's my code:
xaml:
<ListBox Name="ListBox_PuntosIntermedios" MaxHeight="80" Height="80" ScrollViewer.VerticalScrollBarVisibility="Auto">
</ListBox>
And here it is the list of items:
List<string> Lista_punto_intermedio = new List<string>();
All I did in the load method of the wpf window, was this:
Lista_punto_intermedio.Add("testing...");
ListBox_PuntosIntermedios.ItemsSource = Lista_punto_intermedio;
It displays the item "testing..." correctly, but when I add a new item in the list, it's not shown in the Listbox. How can I correct my code to show the items without using ListBox_PuntosIntermedios.Items.Refresh(); that sometimes gives me errors that are not even displayed by the debugger.
I've seen other answers, that say "use inotify..." "use mvvm..." but they don't show you an easy way to do it for noobs like myself.
Thanks in advance for your help
The easiest thing to do in your example is to use an ObservableCollection<string> (in the System.Collections.ObjectModel namespace) instead of a List. ObservableCollection<> will notify your ListBox when items are added/removed to the collection and your UI will update via the magic of WPF data binding. This gets you halfway there as your UI will update when the collection changes (new items will appear in the ListBox and removed items will be removed from the ListBox).
ObservableCollection<string> Lista_punto_intermedio = new ObservableCollection<string>();
Then, you probably want your ListBox to update when one of the strings change as well (example: if "testing..." was updated to "working...", you probably want your ListBox to display "working..."). For this to work with WPF data binding, you need to implement IPropertyNotifyChanged on the objects in your ObservableCollection. To do that, you can introduce a new class with a string property for your text. Maybe something like this:
public class MyNotifyableText : INotifyPropertyChanged
{
private string _myText;
public string MyText {
get { return this._myText; }
set
{
if(this._myText!= value)
{
this._myText= value;
this.NotifyPropertyChanged("MyText");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
This object will send notifications to the WPF data bindings when the "MyText" property is changed and this will allow your ListBox to update accordingly. To tie this new object and your ListBox together, you will have to change your XAML so the ListBox displays your "MyText" property and change the ObservableCollection<string> to be ObservableCollection<MyNotifyableText>
Here's a final code sample:
XAML (note the DisplayMemberPath attribute):
<ListBox Name="ListBox_PuntosIntermedios" DisplayMemberPath="MyText" MaxHeight="80" Height="80" ScrollViewer.VerticalScrollBarVisibility="Auto" >
</ListBox>
List of items:
ObservableCollection<MyNotifyableText> Lista_punto_intermedio = new ObservableCollection<MyNotifyableText>();
Load:
Lista_punto_intermedio.Add(new MyNotifyableText(){ MyText="testing..." });
ListBox_PuntosIntermedios.ItemsSource = Lista_punto_intermedio;
Lastly, I found this tutorial helpful in learning about WPF data binding: http://www.wpf-tutorial.com/data-binding/responding-to-changes/
I created a very simple sample solution here.
If you are developing with WPF and also want to use 2-way binding, your best bet is MVVM. There is a learning curve but it is worth it.
In the sample I used MVVMLight toolkit to simplify a few things. Hope this helps.
I'm currently experimenting with the new compiled bindings and have reached (again) a point where I'm missing a pice in the puzzle: why do I have to call Bindings.Update? Until now, I thought implementing INotifyPropertyChanged is enough?
In my example, the GUI is only displaying correct values, if I call this mysterious method (which is autogenerated by the compiled bindings).
I am using a user control with the following (here simplified) xaml syntax:
<UserControl>
<TextBlock Text="x:Bind TextValue"/>
</UserControl>
where TextValue is a simple dependency property of this user control. In a page, I'm using this control as:
<Page>
<SampleControl TextValue="{x:Bind ViewModel.Instance.Name}"/>
</Page>
where:
ViewModel is a standard propery which is set before InitializeComponent() is run
Instance is a simple object implementing INotifyPropertyChanged
After loading Instance, i raise a property changed event for Instance. I can even debug to the line, where the depency property TextValue of user control gets the correct value -- but nothing is displayed. Only if I call Bindings.Update(), the value is displayed. What am I missing here?
Update
I doesn`t work with {x:Bind ... Mode=OneWay} either.
More code
Person.cs:
using System.ComponentModel;
using System.Threading.Tasks;
namespace App1 {
public class Person : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string name;
public string Name { get {
return this.name;
}
set {
name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
public class ViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private Person instance;
public Person Instance {
get {
return instance;
}
set {
instance = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Instance"));
}
}
public Task Load() {
return Task.Delay(1000).ContinueWith((t) => {
var person = new Person() { Name = "Sample Person" };
this.Instance = person;
});
}
}
}
SampleControl.cs:
<UserControl
x:Class="App1.SampleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="100">
<TextBlock Text="{x:Bind TextValue, Mode=OneWay}"/>
</UserControl>
SampleControl.xaml.cs:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace App1 {
public sealed partial class SampleControl : UserControl {
public SampleControl() {
this.InitializeComponent();
}
public string TextValue {
get { return (string)GetValue(TextValueProperty); }
set { SetValue(TextValueProperty, value); }
}
public static readonly DependencyProperty TextValueProperty =
DependencyProperty.Register("TextValue", typeof(string), typeof(SampleControl), new PropertyMetadata(string.Empty));
}
}
MainPage.xaml:
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:SampleControl TextValue="{x:Bind ViewModel.Instance.Name, Mode=OneWay}"/>
</StackPanel>
</Page>
MainPage.xaml.cs:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace App1 {
public sealed partial class MainPage : Page
{
public MainPage()
{
this.DataContext = new ViewModel();
this.Loaded += MainPage_Loaded;
this.InitializeComponent();
}
public ViewModel ViewModel {
get {
return DataContext as ViewModel;
}
}
private void MainPage_Loaded(object sender, RoutedEventArgs e) {
ViewModel.Load();
Bindings.Update(); /* <<<<< Why ????? */
}
}
}
One more update
I updated the Load method to use task (see the code above)!
Sometimes the data you want to show is not available (like returned from the server or database) until several seconds after your page has loaded and rendered. This is especially true if you call your data in a background/async process that frees up your UI to render without a hang.
Make sense so far?
Now create a binding; let's say something like this:
<TextBlock Text="{x:Bind ViewModel.User.FirstName}" />
The value of your ViewModel property in your code-behind will have a real value and will bind just fine. Your User, on the other hand, will not have a value because it is not returned from the server yet. As a result neither that nor the FirstName property of the User can be displayed, right?
Then your data is updated.
You would think that your binding would automatically update when you set the value of the User object to a real object. Especially if you took the time to make it a INotifyPropertyChanged property, right? That would be true with traditional {Binding} because the default binding mode is OneWay.
What is the OneWay binding mode?
The OneWay binding mode means that you can update your backend model properties that implement INotifyPropertyChanged and the UI element bound to that property will reflect the data/value change. It's wonderful.
Why does it not work?
It is NOT because {x:Bind} does not support Mode=OneWay, it is because it defaults to Mode=OneTime. To recap, traditional {Binding} defaults to Mode=OneWay and compiled {x:Bind} defaults to Mode=OneTime.
What is the OneTime binding mode?
The OneTime binding mode means that you bind to the underlying model only once, at the time of load/render of the UI element with the binding. This means that if your underlying data is not yet available, it cannot display that data and once the data is available it will not display that data. Why? Because OneTime does not monitor INotifyPropertyChanged. It only reads when it loads.
Modes (from MSDN): For OneWay and TwoWay bindings, dynamic changes to the source don't automatically propagate to the target without providing some support from the source. You must implement the INotifyPropertyChanged interface on the source object so that the source can report changes through events that the binding engine listens for. For C# or Microsoft Visual Basic, implement System.ComponentModel.INotifyPropertyChanged. For Visual C++ component extensions (C++/CX), implement Windows::UI::Xaml::Data::INotifyPropertyChanged.
How to solve this problem?
There are a few ways. The first and easiest is to change your binding from ="{x:Bind ViewModel.User.FirstName} to ="{x:Bind ViewModel.User.FirstName, Mode=OneWay}. Doing this will monitor for INotifyPropertyChanged events.
This is the right time to warn you that using OneTime by default is one of the many ways {x:Bind} tries to improve performance of binding. That's because OneTime is the fastest possible with the least memory reqs. Changing your binding to OneWay undermines this, but it might be necessary for your app.
The other way to fix this problem and still maintain the performance benefits that come out of the box with {x:Bind} is to call Bindings.Update(); after your view model has completely prepared your data for presenting. This is easy if your work is async - but, like your sample above, if you can't be sure a timer might be your only viable option.
That sucks of course because a timer implies clock time, and on slow devices like a phone, that clock time might not properly apply. This is something every developer will have to work out specific to their app - that is to say, when is your data fully loaded and ready?
I hope this explains what is happening.
Best of luck!
While "traditional" bindings default to "one-way" (or two-ways in some case), compiled bindings default to "one-time". Just change the mode when setting the binding:
<TextBlock Text="{x:Bind TextValue, Mode=OneWay}" />
Finally I found the bug myself: I was using an task-based operation to load my view model, which resulted in setting the dependency property by the incorrect thread (I think). It works if I set the Instance property via the dispatcher.
public Task Load() {
return Task.Delay(1000).ContinueWith((t) => {
var person = new Person() { Name = "Sample Person" };
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => {
this.Instance = person;
});
});
}
But there was no exception, just the gui displaying no value!
First of all,the default binding mode of x:Bind is OneTime, you need to changed it to OneWay as the answer said above to make it work if you call RaisePropertyChanged method.
It seems like something has wrong with your code of data binding. PLEASE paste all the involved code to let us see the source of this issue.
So I'm pretty new to WPF and MVVM, and while I understand the premise, a lot of this stuff is like trying to read hieroglyphs for me.
Basically, my situation is this: I'm using Activiz, a c# wrapper for VTK, which is an image processing/visualization library. So, in this library, there's a WinForms control called vtk:RenderWindowControl, which is an opengl control containing the class that handles all of the visualization functionality. I think it'd be easier to just use WinForms, but that's not really an option for me.
So, to use vtk:RenderWindowControl in a WPF application, I just need to shove it into a WindowsFormsHost and then I can use it more or less just like the example code, in the code behind (if that's the correct term for the .xaml.cs file)
That's fine for a test app, but in practice, I'd like to follow MVVM if possible. This is where I've run into a wall. If "renderControl" lives in the View class, how can I reference it and use it from the ViewModel? I think binding is the answer to that question, but I only really know how to do that for simple types and commands.
Following ideas in another thread I found, I managed to set up something like this answer
My codebehind looks like this:
public partial class RenderPanel_View : UserControl
{
public static readonly new DependencyProperty RWControlProperty =
DependencyProperty.Register("RWControl", typeof(RenderWindowControl), typeof(RenderPanel_View), new PropertyMetadata(null));
public RenderWindowControl RWControl
{
get { return (RenderWindowControl)GetValue(RWControlProperty); }
set { SetValue(RWControlProperty, value); }
}
public RenderPanel_View()
{
// This is necessary to stop the rendercontrolwindow from trying to load in the
// designer, and crashing the Visual Studio.
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) {
this.Height = 300;
this.Width = 300;
return;
}
InitializeComponent();
this.RWControl = new RenderWindowControl();
this.RWControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.WFHost.Child = this.RWControl;
}
}
My .xaml looks like this
<UserControl x:Class="vtkMVVMTest.RenderPanel.RenderPanel_View"
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:vtk="clr-namespace:Kitware.VTK;assembly=Kitware.VTK"
xmlns:rp="clr-namespace:vtkMVVMTest.RenderPanel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
RWControl="{Binding VMControlProperty}">
<Grid>
<WindowsFormsHost x:Name ="WFHost"/>
</Grid>
</UserControl>
So two things. One, That last line of the xaml header is an error, "The member 'RWControl' is not recognized or accessible". I don't really understand why. Second, for what I'm guessing is the ViewModel half of the equation, how is VMControlProperty supposed to be defined?
Am I at least on the right track here, or is this way off base?
Some controls are not MVVM friendly and you have make the ViewModel aware of View interface and allow interact with it directly. Do not open the whole control to the ViewModel it will ruin the ability to write tests, put an interface on top for example IRenderPanelView and open in the interface only the functionality you need to access from ViewModel. You can then create a DP property of this type in the view, set it in the constructor and bind it to ViewModel.View property in xaml.
I asked about how does it work with INotifyPropertyChanged interface( How does WPF INotifyPropertyChanged work? ), and it requires me to connect XAML's DataContext to the INotifyPropertyChanged inherit instances as follows.
MainViewModel model = new MainViewModel();
this.DataContext = model;
And I also found a recommendation to have a comment for DataContext that each XMAL uses( http://joshsmithonwpf.wordpress.com/2009/10/24/xaml-tip-datacontext-comment/ ).
When I have multiple XAML files, and when I want to link the DataContext to different ViewModel, I guess I need to make the each XAML.CS file to contain this code (model varies for each xaml.cs) :this.DataContext = model;.
Is this correct?
How can I do the same thing in XAML file?
What's the magic behind this DataContext thing? I mean, how does DataContext work?
The DataContext is really one of the main keys to the binding system in WPF. When you design your View (the XAML), you're setting up data bindings, but these are all being done by name (effectively, as a string). The "nearest" DataContext up the visual hierarchy is the object that WPF uses to find the matching property (by name) and wire up the binding.
The suggestion of putting the comment in place is a good one - it helps because the names chosen really depend on the ViewModel (DataContext), so a View's XAML file is really tied to a specific type of DataContext.
Note also that there are other approaches available to wire up the DataContext other than setting it in code behind, including using locators, DataTemplates, setting it directly in XAML, etc.
Yes that is correct as far as i know, since this is quite repetetive some MVVM frameworks do this linking for you.
In XAML:
<UserControl ...
xmlns:vm="clr-namespace:MyApp.ViewModels">
<UserControl.DataContext>
<vm:MyViewModel />
</UserControl.DataContext>
<!-- ... -->
</UserControl>
It enables short bindings where the Path is relative to the DataContext, e.g. {Binding Name} binds to DataContext.Name. It also is inherited which can be useful.
Please read the Data Binding Overview if you haven't.
1 - The INotifyPropertyChanged interface updates the property changes to the UI,
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
2- We can use two ways to set the data context to the view , one option is to set the context in code behind file, but this is tightly coupled with view and its not a good approach, i would suggest the below option, its loosly coupled with the view
<UserControl ...
xmlns:vm="clr-namespace:MyApp.ViewModels">
<UserControl.DataContext>
<vm:MyViewModel />
</UserControl.DataContext>
<!-- ... -->