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).
Related
I do have a ListDetailsView showing some data (lets say Company as a simple example here). Normally the details of a Company are shown as readonly. However, via the ListDetailsView.DetailsCommandBar it is possible to edit a Company (and also add a new Company). A clear separation between view and edit mode seems to be a good choice for the UI. I'm using a UserControl to show details of a Company.
So here are my questions:
Where should the differentiation between view- and edit-mode happen? I thought it is a good idea to have a CompanyDetailsControl and a CompanyDetailsEditControl and select between the two (both using the same CompanyDetailsViewModel). There are other solutions as well, for example, the CompanyDetailsControl could handle the edit- and view-mode internally.
Assuming that it is a good idea to switch between two UserControl, how can that be realized with the ListDetailsView.DetailsTemplate? I though it would be easy to use a DataTemplateSelector here, but that is only available for the ItemTemplate.
Not sure what code to provide to clarify my questions. So in case you need any code to better understand my question please leave a comment.
Note: I have never worked with UWP app, only applying MVVM pattern from WPF.
Straight line where the split should happen is not drawn. It often depends on the developer himself, which framework is used and more.
Me personally would go in way where UI handles UIs' things and ViewModel handles data only. That means the view is responsible for showing you the controls you are expecting to see/control the application. And when the view learns that property was changed, it should update how it looks.
Since the point we know the app will have edit & readonly modes, we should prepare all necessary UI components (UserControls, Pages, ...) to handle both those states. They would be binded to ViewModel that have base in BaseViewModel that already have this edit variable inside. So that each UI component know it can work with that.
Base view model:
abstract class BaseViewModel : INotifyPropertyChanged
{
private string mIsInEditMode;
public string IsInEditMode
{
get { return mIsInEditMode; }
set
{
if(mIsInEditMode == value) return;
mIsInEditMode = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(IsInEditMode));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
All "normal" ViewModels inherit from it:
class CompanyViewModel : BaseViewModel
{ /* props and logic of company */ }
UI component (UserControl) would have either trigger (<Style.Triggers/>) or binded properties Visibility and IsEnabled to the BaseViewModel. Those bindings would handle this logic of showing/hiding and you have potential to control whole layouts, hide controls etc.
<UserControl d:DataContext="{x:Bind local:CompanyViewModel}">
<UserControl.Resources>
<local:BoolInverterConverter x:Key="BoolInvert"/>
</UserControl.Resources>
<Grid>
<Grid IsVisible="{Binding IsInEditMode}" IsEnabled="{Binding IsInEditMode}">
<!-- Controls for edit mode -->
</Grid>
<Grid IsVisible="{Binding IsInEditMode, Converter={StaticResource BoolInvert}}"
IsEnabled="{Binding IsInEditMode, Converter={StaticResource BoolInvert}}">
<!-- Controls for readonly mode -->
</Grid>
</Grid>
</UserControl>
Note: I've used property IsVisible, You would actually use Visibility with some custom converter.
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.
Hello im new to making apps with WPF and XAML in Visual Studio. So I have a grid I want to change its properties in the code.
My Grid's properties:
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286">
How I have been trying to change its properties
this.usersPan.SetValue(Grid.WidthProperty, PAN_SIZE);
usersPan.SetValue(Grid.WidthProperty, PAN_SIZE);
usersPan.Width = 0;
usersPan.Visibility = Visibility.Collapsed;
When I try to do that^ it says null reference for userPan
Thanks
Noooooooo, Don't ever do that. Make a ViewModel that is bound to the Grid's Width property, and then just change the value.
My suspicion is that you do not need this at all. Have a look into containers, and how to position them.
In all of this years, there have been rare occasions I needed to do that and I suspect you do not need to. Tell me what you are doing.
EDIT:
You have a VM which needs to implement the NotifyPropertyChanged interface (I won't do that here, there are plenty of examples on hoew to do that)
public class MainVM
{
public ObservableCollection<TabVM> TabsVms {get;set;}
public int SelectedIndex {get;set}
}
bound to the control
<TabControl DataContext={TabsVMs} SelectedIndex="{Binding SelectedIndex}">
...
</TabControl>
And in runtime you create a couple of Tabs
var TabsVMs = new ObservableCollection<TabVM>();
TabsVMs.add(new TabVM());
TabsVMs.add(new TabVM());
TabsVMs.add(new TabVM());
Then in runtime you change the value of the index.
MainVm.SelectedIndex = 1
and the the coresponding tab will become selected.
EDIT:
I can also recommend you to use Fody for the MVVM notification.
Also, when it comes to bindings, I can recommend you to use WPF inspector. a handy little tool
The best way to write WPF programs is to use the MVVM (Model-View-View Model) design pattern. There are two (2) ideas behind MVVM:
Write as little code as possible in the view's code-behind and put all of the logic in the View Model object, using WPF's data binding feature to connect the properties of the View Model object to the view's controls.
Separate the logic from the display so you can replace the view with some other construct without having to change the logic.
MVVM is a huge topic on its own. There are lots of articles about it, and frameworks that you can use to build your program. Check out MVVM Light, for example.
Don't know exactly why Grid is invisible in code-behind, but You can access it's properties using events (but don't think it is perfect solution).
For example add to your grid event Loaded
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286"
Loaded="FrameworkElement_OnLoaded">
and then from code-behind you can access grid in next way:
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var grid = sender as Grid;
if (grid != null)
{
grid.Width = 0;
}
}
Better solution :
Add some boolean property to your ViewModel like public bool IsGridVisible{get;set;}
And bind it to your Grid
<Grid HorizontalAlignment="Left"
Height="603"
Margin="0,51,0,0"
x:Name="usersPan"
VerticalAlignment="Top"
Width="1286"
Visibility="{Binding Path=IsGridVisible, Converter={StaticResource BoolToVis}">
where BoolToVis is converter which converts true to Visible and false to Hidden. You can define it in App.xaml like :
<BooleanToVisibilityConverter x:Key="BoolToVis" />
I was able to do something like this so I can change properties outside of an event.
private Grid userGrid;
private void onUserGridLoaded(object sender, RoutedEventArgs e)
{
userGrid = sender as Grid;
}
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
I'm new to WPF, trying to adhere to the MVVM pattern as best as I can. So far so good except I've run into a problem with binding certain properties from my model.
So I have non-static properties that I have exposed in my model but they can only be changed from within the model. I run some function that does a bunch of stuff and it keeps track of what it's doing through a bunch of parameters that I've exposed for viewing.
I'm ok when I've got properties in my ViewModel - I can update these ok because I have implemented INotifyPropertyChanged. I've seen that sometimes people have implemented this in their Model too so I tried that but not knowing how INotifyPropertyChanged really works I don't know if there's anything else I need to do to get it running ok once it's in the Model.
I tried to create a property in my ViewModel that read from the Model and I bound the xaml to this but because I can't alter it from the ViewModel I ran into the same problem of telling the UI that it has been changed. Currently I have the binding direct whilst I try and figure this out but my goal is to be able to bind to a property in the ViewModel that just grabs the value from the Model.
Can anyone give me a good simple example of one-way binding to basic controls like labels/textblocks etc that will update itself when it all changes from within the model?
For completeness here is a simplified version of what I have including sample xaml (showing binding to a Model property & binding to a property from the ViewModel). The binding works because if I make changes in the model they appear in the designer and initial build.
The model is my own code and I can add/remove anything to get it working. Maybe it's fairly straightforward but I'm just not seeing the solution at the moment and not seen anything that makes sense to me on the forums.
Thanks!
in the Model
public enum TempValues { zero, pos10, pos50, pos100 }
namespace AutoCalModel
{
public class AutoCalibration : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private TempValues _TempRelayValue = TempValues.zero;
public TempValues TempRelayValue
{
get { return _TempRelayValue; }
set
{
if (!value.Equals( _TempRelayValue))
{
_TempRelayValue = value;
NotifyPropertyChanged("TempRelayValue");
}
}
}
// rest of class including code that changes the above TempRelayValue
// accessed through the public property only
}
}
in the xaml
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Name="labelPrsTitle" Text="Prs:" Margin="2,0,2,0"/>
<TextBlock Name="labelPrsValue" Text="{Binding Path=currentPrsValueString, Mode=OneWay}" Margin="2,0,5,0"/>
<Separator Margin="5,0,5,0"/>
<TextBlock Text="Temp Relay:" Margin="5,0,2,0"/>
<TextBlock Text="{Binding Path=TempRelayValue, Converter={StaticResource tempValuesConverter}, Mode=OneWay}" Margin="2,0,5,0">
<TextBlock.DataContext><Model:AutoCalibration/></TextBlock.DataContext>
</TextBlock>
</StackPanel>
One of my friend is also doing the same mistake.
// rest of class including code that changes the above **_TempRelayValue**
here you have mansioned that you will change the _tempRelayVAlue varialble. A variable don't have any notification assocated with it. So what you have to do is set the value via proerty like below and this should notify the UI that model or VM values has chagned. as you have implemanted the notifycation into properties not in variables.
TempRelayValue = yourvalues;