Bind to UIElement in code-behind crashing - c#

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!

Related

ContentControl Content Property not changing with hosted content

I am trying to learn MVVM and have come across a weird snag. I have a main menu with a drawer control that comes out and shows a menu:
In the main window where this drawer is, I have a ContentControl where I set its content with a Binding.
<ContentControl x:Name="MainWindowContentControl" Content="{Binding Path=WindowContent}"/>
This window's binding is set to a view model.
<Window.DataContext>
<viewmodels:MainWindowViewModel/>
</Window.DataContext>
and here is the ViewModel:
MainWindowViewModel.cs
public class MainWindowViewModel: ViewModelBase
{
private object _content;
public object WindowContent
{
get { return _content; }
set
{
_content = value;
RaisePropertyChanged(nameof(WindowContent));
}
}
public ICommand SetWindowContent { get; set; }
public MainWindowViewModel()
{
SetWindowContent = new ChangeWindowContentCommand(this);
}
}
So far up to this point, everything works fine. So for example, if I click "Recovery Operations", I get this:
RecoveryOperationsView.xaml
In "RecoveryOperationsView.xaml" (which is a UserControl) I also reference the view model from above like so..
<UserControl.DataContext>
<viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
and have a button to call the command to change the Content property of the ContentControl from the main window..
<Button Grid.Row="2" Content="Restore Database" Width="150" Style="{StaticResource MaterialDesignFlatButton}" Command="{Binding SetWindowContent}" CommandParameter="DatabaseRecovery" >
In my class to process the commands, I change the content based off of the passed parameter using a switch statement like so
ChangeWindowContentCommand.cs
public class ChangeWindowContentCommand : ICommand
{
private MainWindowViewModel viewModel;
public ChangeWindowContentCommand(MainWindowViewModel vm)
{
this.viewModel = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
switch (parameter)
{
case "Home":
viewModel.WindowContent = new HomeView();
break;
case "RecoveryOps":
viewModel.WindowContent = new RecoveryOperationsView();
break;
case "DatabaseRecovery":
viewModel.WindowContent = new DatabaseRestoreView();
break;
}
}
}
However, this is where I get lost... If I click something within this new window, say "Restore Database" and inspect it with a breakpoint, I can see the property being changed but the actual ContentControl Content property doesnt change to the new UserControl I made... I can change the content with anything in the drawer, but if I try to click a button in the hosted Content of the ContentControl nothing changes. What am I missing?
It's hard to be 100% sure without having your project to test with, but I am fairly confident that at least one of the issues is that your UserControl and your MainWindow use different instances of the MainWindowViewModel. You do not need to instantiate the VM for the user control, as it will inherit the DataContext from the MainWindow. The way it works in WPF is that if any given UIElement does not have theDataContext assigned explicitly, it will inherit it from the first element up the logical tree that does has one assigned.
So, just delete this code, and it should solve at least that issue.
<UserControl.DataContext>
<viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
And since you're learning WPF, I feel obligated to provide a couple other tips. Even though you're using a ViewModel, you are still mixing UI and logic by creating a very specific implementation of ICommand and assigning a UI element through your ViewModel. This breaks the MVVM pattern. I know MVVM takes a little time to understand, but once you do, it is very easy to use and maintain.
To solve your problem, I would suggest creating View Models for each of your user controls. Please see this answer, where I go into quite a bit of detail on the implementation.
For switching the different views, you have a couple of options. You can either use a TabControl, or if you want to use a command, you can have a single ContentControl bound to a property of MainWindowViewModel that is of type ViewModelBase. Let's call it CurrentViewModel. Then when the command fires, you assign the view model of the desired user control to that bound property. You will also need to utilize implicit data templates. The basic idea is that you create a template for each of the user control VM types, which would just contains an instance of the Views. When you assign the user control VM to the CurrentViewModel property, the binding will find those data templates and render the user control. For example:
<Window.Resources>
<DataTemplate DataType = "{x:Type viewmodels:RecoveryOperationsViewModel}">
<views:RecoveryOperationsView/>
</DataTemplate>
<!-- Now add a template for each of the views-->
</Window.Resources>
<ContentControl x:Name="MainWindowContentControl" Content="{Binding CurrentViewModel}"/>
See how this approach keeps UI and logic at an arm's length?
And lastly, consider creating a very generic implementation of ICommand to use in all your ViewModels rather than many specific implementations. I think most WPF programmers have more or less this exact RelayCommand implementation in their arsenal.

WPF Game editor - Updating properties in UI/code

I'm trying to create a game editor in C#/WPF. The editor consists of a user control that shows the scene (rendered in OpenGL using SharpGL) as well as many controls for editing the current scene and objects. Objects consist of components whose properties can be edited in the editor (kind of like in Unity game engine). I already have a "component editor" view which uses reflection to find all properties on the component and creates a property editor (for example, a slider) per each property. However, I'm not sure how to bind the properties between UI and code.
The problem is, I want these properties to be updated in the UI when they change in code, as well as updated in code when they're changed in the UI. If I want to bind the editor controls (such as a slider that changes a property) to the component properties, they would have to implement NotifyPropertyChanged, which would be quite cumbersome. I guess the other way is doing dirty-checking, but I'm not sure if that's a good idea either.
Can anybody give me pointers on how this property updating between UI/Code should be handled? I want it to work pretty much like it does in Unity, where you don't need to write anything extra into your component class to make properties editable.
EDIT: To make more clear what I'm trying to achieve and already have, here is a part of the "component editor" user control. It's datacontext is a Component instance (model). PropertiesConverter returns it's properties (through component.GetType().GetProperties()). ComponentPropertyTemplateSelector decides on the property editor user control (for example, for a double property it would select a "number editor" that has a textbox for editing the value). The problem that I'm interested in solving is how to two-way bind a Component's property to an editor control.
<ItemsControl x:Name="ComponentProperties" Grid.Row="1" ItemTemplateSelector="{StaticResource ComponentPropertyTemplateSelector}">
<ItemsControl.ItemsSource>
<Binding Converter="{StaticResource PropertiesConverter}"/>
</ItemsControl.ItemsSource>
</ItemsControl>
I would say you probably want to follow the MVVM pattern which does use the INotifyPropertyChanged interface. If you do a Google search on MVVM there are some good articles that come up right away. There are also some existing tools out there already to help get you started. From what you describe in your question the MVVM pattern essentially works that way. It decouples the UI and the code but still maintains that connection. The real quick version is that you implement the INotifyPropertyChanged on a class and then you set an instance of that class to the DataContext of the control you want to setup the binding for. Probably easier to see an example:
Xaml:
<StackPanel>
<Slider Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding MyText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding MyText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Slider Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
I created a view model base to save on some code writing:
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
An example view model class:
class MyViewModel : ViewModelBase
{
private int sliderValue;
private string myText;
public int SliderValue
{
get { return this.sliderValue; }
set
{
this.sliderValue = value;
this.OnPropertyChanged();
}
}
public string MyText
{
get { return this.myText; }
set
{
this.myText = value;
this.OnPropertyChanged();
}
}
}
How to hook up the binding (in this case the code behind of the control):
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
As you can see there is some work involved in setting up the view models and xaml. Compared to other solutions I think this is pretty good as far as the amount of "work" you have to put in. I don't know if there is any way to get around it though and have it work like "magic". It might be worth checking into what MVVM tools exist, there may be stuff out there that can make things even more simple.
You can add IPropertyChangeNotification support automatically using either Castle Dynamic Proxy (which wraps the classes in a proxy) or Fody (which modifies the IL in a post-build step).

Disabling multiple controls depending on a state

I'm new to both Caliburn and WPF, so excuse me if it is a rather trivial question.
The scenario is the following:
I have multiple controls (like buttons and textboxes - the latter is the important part).
Their state (Enabled/Disabled) are dependent on a boolean property.
The first suggested method I tried was using the Can[FunctionName] convention and NotifyOfPropertyChange(() => Can[FunctionName]). It worked well with the button, but it did not work with the textbox.
How do I bind IsEnabled property to a state without using the code-behind of the View?
The code I tried in the ViewModel didn't work for the textbox:
private bool _buttonEnableState = true;
public bool ButtonEnableState
{
get
{
return _buttonEnableState;
}
set
{
_buttonEnableState = value;
NotifyOfPropertyChange(() => CanTheButton);
NotifyOfPropertyChange(() => CanTheTextBox);
}
}
public bool CanTheButton
{
get
{
return ButtonEnableState;
}
}
public void TheButton()
{
}
public bool CanTheTextBox
{
get
{
return ButtonEnableState;
}
}
From the View:
<Button x:Name="TheButton" Content="This is the button" ... />
<TextBox x:Name="TheTextBox" ... />
Thanks in advance!
Have you tried the obvious?:
<Button Content="This is the button" IsEnabled="{Binding ButtonEnableState}" />
<TextBox x:Name="TheTextBox" IsEnabled="{Binding ButtonEnableState}" />
UPDATE >>>
So, continuing the conversation from the comments... now you have a public property in your AppViewModel class and an instance of that class is set as the DataContext of your view that contains the Button and TextBox controls?
Let's see if the Binding is really working or not... try changing your code to this:
<Button Content="{Binding ButtonEnableState}" />
If the Button.Content is set then the Binding works just fine and you have a different problem.
UPDATE 2 >>>
As #Charleh mentioned, you also need to make sure that you have notified the INotifyPropertyChanged interface of the change of property value:
NotifyOfPropertyChange(() => ButtonEnableState);
I don't think what I'm about to suggest is necessarily the correct way of doing things, but it may give you the result you're after.
In order to get the control to be disabled based on a Can<name> property, you need to confirm to the conventions that Caliburn uses, so in this case, supplying a function <name> should work:
public void TheTextBox()
{
}
As a result of the default conventions, I believe this will be called every time the KeyDown event is fired.
That said, you probably want to bind your text content to something, and you'll want to use the x:Name property convention to choose which property, that means you'll have to attach the TheTextBox() function in a different way, you should be able to do that using the Message.Attach property in the Caliburn namespace.
So your TextBox could look like this (where you've added the following namespace xmlns:cal="http://www.caliburnproject.org"):
<TextBox cal:Message.Attach="TheTextBox" Name="SomeTextProperty" />
Backing that up in your ViewModel, you'd have:
// Your Enabled Property (using your existing code).
public bool CanTheTextBox
{
get
{
return ButtonEnableState;
}
}
// Your dummy function
public void TheTextBox()
{
}
// Some text property (Just for demo, you'd probably want to have more complex logic in the get/set
public string SomeTextProperty
{
get; set;
}
You should then see the Enabled/Disabled behaviour, and be use the SomeTextProperty.
I'm not entirely sure I like this way of doing things, I just had a quick play to see if it worked. The following answer might be a cleaner solution, and establishes a new re-usable convention:
Adding a convention for IsEnabled to Caliburn.Micro
As a slight aside (not a direct answer), depending on how complicated your control/form is, you could investigate using multiple Views for the same ViewModel, in the past I've set up a ReadOnly and Editable view, and used a single property on the ViewModel to toggle between the two (essentially setting the entire state of the ViewModel). There are already default conventions so you can use multiple views with relative ease.

What happens internally when you bind to ItemSource?

I'm curious how this works, because I have a MainViewModel, which has Property say called SubViewModel which has a Property of ObservableCollection (we'll call it Property1.)
I've implemented INotifyChangedProperty on everything.
My Main Window
<Window ..
DataContext="{Binding MainViewModel}" />
...
<StackPanel DataContext="{Binding SubViewModel}">
<local:SomeControl DataContext="{Binding}" />
</StackPanel>
</Window>
And my UserControl
<UserControl Name="SomeControl">
<DataGrid Name="MyDataGrid" ItemSource="{Binding Property1, Mode=TwoWay}" CurrentCellChanged="TestMethod" />
...
</UserControl>
In my test method, just as a test to figure out why the changes are not propegating up to the main view model I do something like this
private void TestMethod()
{
var vm = this.DataContext as SubViewModel;
var itemSourceObservableCollection = MyDataGrid.ItemsSource as ObservableCollection<MyType>;
//I thought vm.Property1 would be equal to itemSourceObservableCollection
//but they are not, itemSourceObservableCollection shows the changes I've made
//vm.Property1 has not reflected any changes made, even though I though they were the same item
}
So I figured out that ItemSource must create a copy of the item you bind it to? I'm stuck here, how do manually notify the viewModel that this property has changed and it needs to update? I thought that was INotifyPropertyChanged's job?
I think part of my problem is I lack the understanding of how this kinda works internally. If anyone can point to a good blog post, or documentation to help me understand why my code isn't working the way I expected, that would be great.
1) No copy is made.
2) ObservableCollection will propogate changes made to the collection, not the items within the collection. So you'll see additions, deletions etc. but NOT property changes to items within the collection.
3) If you want to see changes made to individual items in the ObservableCollection, you need to implement INotifyPropertyChanged on those items.
There's actually TWO different issues here. What happens internally when you bind to a collection? AND why changes on the user surface are not propagated back to your View Model. Based upon what you wrote, the two issues are not connected, but let's take them one at a time...
For the first issue... When you bind a collection, the WPF binding engine creates a "CollectionView" class that mediates between your object store and the logical tree. You can, if needed, get a copy of the the "CollectionView" using a static method on CollectionViewSource...
var cvs = CollectionViewSource.GetDefaultView(MyCollectionOfThings);
There are several interesting properties in the result, and some of them contain write accessors which allow you to directory modify the CollectionView.
For the second issue... The business classes in your SubViewModel need to inherit from INotifyPropertyChanged such that changes are 'announced' via the WPF binding engine. Your VM should be a publisher, but can also be a subscriber. A property that participates in the INotifyPropertyChanged plumbing gets declared like this...
private string _name;
[Description("Name of the driver")]
public string Name
{
[DebuggerStepThrough]
get { return _name; }
[DebuggerStepThrough]
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
This code publishes changes, but can also subscribe to changes made on the user surface by setting the appropriate attributes in your Xaml.
Background reading: What is a CollectionView?
Also, Similar question

XAML and WPF - Passing Variables to XAML Windows

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.

Categories

Resources