WPF Custom Controls - c#

I've been trying for ever to try and figure this out.
Story: I have one MainWindow and 2 User Controls.
When the MainWindow loads One control is visible and the other is not.
Once the user enters their data and settings, I need to make the other form visible.
The form that is invisible at startup needs to be initialized, because it is gathering data from the WMI of the computer it is running on. It is also gathering AD Information in preparation for the user.
For some reason I cannot get one form to show the other.
I think this is what I'm supposed to be looking at:
#region Class Variable
public string ShowSideBar { get { return (String)GetValue(VisibilityProperty); } set { SetValue(VisibilityProperty, value); }}
public DependencyProperty VisibilityProperty = DependencyProperty.Register("ShowSideBar", typeof(string), typeof(UserControl), null);
#endregion
This is set in my MainWindow Class, however, I have no idea why I cannot call it from any other usercontrol.
Is there any way to just expose something like this to all my forms from my MainWindow?
public int RowSpan {
get { return Grid.GetRowSpan(DockPanel1); }
set { Grid.SetRowSpan(DockPanel1,value); }
}

Dependency properties must be static. Why is the type string? Should it not be Visibility if you wish to bind the visibility of the controls to it?
Does it have to be a dependency property? You could just use a regular property as well and implement INotifyPropertyChanged, since you are not binding this field to anything, rather binding other things to it.
For a dependency property, try something like this instead:
public static readonly DependencyProperty SideBarVisibilityProperty = DependencyProperty.Register("SideBarVisibility", typeof(Visibility), typeof(MyTemplatedControl), null);
public Visibility SideBarVisibility
{
get { return (Visibility)GetValue(SideBarVisibilityProperty); }
set { SetValue(SideBarVisibilityProperty, value); }
}

Firstly, this application would benefit from application of the MVVM pattern.
However, without taking that approach, you can still resolve the problem you have. It would be unusual for a user control to rely on knowing what its parent is. The code behind for your main window would be the better place to put this code. (Not as good as a view model... but that's another story.)
Add to the control that should cause the side bar to be made visible an event, ShowSideBar. Attach a handler in the main window, and use the handler to display the second control. No need for dependency properties here at all.
public class MyControl : UserControl
{
...
public event EventHandler ShowSideBar;
// Call this method when you need to show the side bar.
public void OnShowSideBar()
{
var s = this.ShowSideBar;
if (s != null)
{
s(this, EventArgs.Empty);
}
}
}
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.FirstControl.ShowSideBar += (s, e) =>
{
this.SecondControl.Visibility = Visibility.Visible;
}
}
}

I fixed the initlized Component but changing.
X:Class="AdminTools.MainWindow.ShowSideBar" to x:Class="AdminTools.MainWindow".
now i have an issues where
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:User="clr-namespace:AdminTools.Controls.User"
xmlns:Custom="clr-namespace:AdminTools.Controls.Custom"
xmlns:Bindings="clr-namespace:AdminTools.Functions"
x:Class="AdminTools.MainWindow"
Title="MainWindow" Height="691.899" Width="1500"
>
<Window.DataContext>
<Bindings:ShowSideBar />
</Window.DataContext>
<Bindings:ShowSideBar /> = ShowSideBar does not exist in the namespace clr-namespace:AdminTools.Functions
ShowSideBar: member names cannot be the same as their enclosing type.

Related

How to achieve dynamic binding in WPF/MVVC C#

I am rather new to MVVC/wpf, having mostly worked with winforms.
What I want to accomplish is dynamic databinding without using code behind in WPF. The user interface consists of a devexpress grid and a couple of buttons. Each button press loads an object list and presents the objects in the grid. The lists contain different object types depending on the button pressed. For this example I have two classes to present: FatCat and FatDog.
In winforms this works:
private void button1_Click(object sender, EventArgs e)
{
((GridView)gridCtrl.MainView).Columns.Clear();
gridCtrl.DataSource = new BindingSource(itsModel.GetAll<FatDog>(), null);
}
private void button2_Click(object sender, EventArgs e)
{
((GridView)gridCtrl.MainView).Columns.Clear();
gridCtrl.DataSource = new BindingSource(itsModel.GetAll<FatCat>(), null);
}
I have configured the grid to create columns dynamically, so everything just works. itsModel is of type CatClientModel.
In wpf I have defined the DataContext to be CatClientModel.
What should I use for ItemsSource in the grid to achieve the same behaviour as my winforms solution?
dxg:GridControl ItemsSource="{Binding SomeDynamicList}"
In other words, what should SomeDynamicList be in the code above? Or am I going about this the wrong way?
I am, as I stated, using the DevExpress wpf grid control, but the question ought to be general and apply to any control presenting object lists.
In other words, what should SomeDynamicList be in the code above?
SomeDynamicList should be an ObservableCollection<T> property to which you can add any objects of type T that you want to display in the GridControl.
Set the DataContext of the GridControl, or any of its parent elements, to an instance of a class where this property is defined:
public class CatClientModel
{
public ObservableCollection<Animal> SomeDynamicList { get; } = new ObservableCollection<Animal>();
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new CatClientModel();
}
}
Ok. But the thing is that the ObservableCollection contains different types. Unfortunately there is no feasible class to inherit from. I want to bind to either ObservableCollection or ObservableCollection depending on which button was pressed
Switch the DataContext then, or change the property into an IEnumerable and set it to a new collection each time the button is clicked. This requires you to implement the INotifyPropertyChanged interface in your view model
private System.Collections.IEnumerable _collection;
public System.Collections.IEnumerable MyProperty
{
get { return _collection; }
set { _collection = value; OnPropertyChanged(); }
}
If you want to use XAML to define which data sources your code maps to for each grid that is possible. That does require at least some method of MVVM manager either prism or mvvmlight to connect the view model to the view.
so if you do go the MVVM model route, the Model would contain a description for each of your grids like this:
public BulkObservableCollection<icd10facet> FacetList
{
get { return this._facets; }
set { SetProperty(ref this._facets, value); }
}
public INotifyTaskCompletion<BulkObservableCollection<PetsConvert>> ConceptList
{
get { return this._concept; }
set
{
SetProperty(ref this._concept, value);
}
}
In the XAML for your code the grid woud bind to the grid defined by ConceptList in this way:
ItemsSource="{Binding ConceptList.Result}"
this answer does NOT address how to wire up Prism 6.0 for example to use a view model but for examples see:
https://github.com/PrismLibrary/Prism
Which contains documentation and starter code. Keep in mind that there is not any specific reason that putting code in the code behind for the view is a problem, first solve the problem and then refactor if separation of concerns is an issue for you.
Using this technique you can bind each grid to its own data source. In the MVVM space buttons and other things use a commanding model to communicate with the view model.
<Button Content="Load Rule Data" Width="100" Height="40" HorizontalAlignment="Left" Margin="5px" Command="{Binding LoadRuleData }"/>
this requires defining a command delegate in the viewmodel for LoadRuleData
public DelegateCommand LoadRuleData { get; private set; }
and then (usually in the constructor) wire the DelegateCommand to the method that is going to do the work.
this.LoadRuleData = new DelegateCommand(this.loadRules);

UWP (XAML / C#) access method from parent Page within a programmatically added UserControl

I've created a UWP XAML Page where I programmatically add a UserControl and then is displayed in a ContentControl.
UserControl control = new Views.PlayingGameScreen();
this.ContentControl.Content = control;
I'm wondering how I access a method in the parent Page from the UserControl.
I think the cleanest way is to expose a property on the UserControl that the parent sets.
public partial class PlayingGameScreen : UserControl
{
public ICommand Command { get; set; }
public void SomeMethod()
{
this.Command?.Execute(null);
}
}
//parent code, define the command with the logic to run
var command = new DelegateCommand(...);
var control = new Views.PlayingGameScreen
{
Command = command
};
this.ContentControl.Content = control;
By doing it this way, the parent sets the Command and the UserControl doesn't need to know what the hosting parent is and there is no global class the UserContol uses. This is a completely decoupled solution.
Bonus, setup the Command as a DependencyProperty and then it can be assigned in xaml as well. You can bind the Command to your ViewModel and the ViewModel will be the Page's ViewModel.
<uc:PlayingGameScreen Command="{x:Bind SomeCommand}"/>
I've tried lots of different ways and this is my favorite.
From my little experience with UWP (1 app and counting :D):
You can keep a reference of your ParentPage in a static property of a class, let's name it PagesGateway.cs
The static class code:
public static class PagesGateway
{
public static ParentPage ParentPage {get; set;}
}
Then in you code behind ParentPage, you'll need to set the reference:
public partial ParentPage:Page
{
public ParentPage{
PagesGateway.ParentPage = this;
}
}
This way you'll have access to ParentPage instance methods anywhere.
But I have a feeling that's not the best practice out there. But i use this a lot to access my ViewModels from anywhere, and it work perfectly, but you have to take into account that it void the re-usability of a ViewModel

wpf c# data binding to set string using property of viewModel object

I am trying to solve this issue for so many hours:
I have user custom control of grid named NewMazeGrid and I want to use it as a control in MainWindow. MainWindow contains MazeViewModel(mazeVM member).
I'm trying to set the values of the grid, when the property MazeViewModel:MySingleplay changes.
(I'm using the INotifyPropertyChanged for it, and it works perfectly fine. I guess, the problem is in the final binding)
The code:
This is the property MazeViewModel:MySingleplay getter:
public string MySingleplay
{
get
{
if (myModel.MySingleplay == null)
{
return "";
} else
{
return myModel.MySingleplay.ToString();//works perfect
}
}
}
this is the NewMazeGrid.xaml.cs:
namespace VisualClient.View.controls
{
public partial class NewMazeGrid : UserControl
{
private MazePresentation myMaze;
private string order; //dont really use it
//Register Dependency Property
public static readonly DependencyProperty orderDependency =
DependencyProperty.Register("Order", typeof(string), typeof(NewMazeGrid));
public NewMazeGrid()
{
myMaze = new MazePresentation();
InitializeComponent();
DataContext = this;
lst.ItemsSource = myMaze.MazePuzzleLists;
}
public string Order
{
get
{
return (string)GetValue(orderDependency);
}
set
{
SetValue(orderDependency, value);
myMaze.setPresentation(value); //(parsing string into matrix)
}
}
}
}
this is the MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private MazeViewModel mazeVM;
public MainWindow()
{
InitializeComponent();
mazeVM = new MazeViewModel(new ClientMazeModel(new TCPClientConnection()));
DataContext = mazeVM;
mazeVM.connectToServer();
}
private void bu_Click(object sender, RoutedEventArgs e)
{
bool isC = mazeVM.isConnected();
mazeVM.openSingleplayGame("NewMaze");//works perfect
}
this is the MainWindow.xaml:
<Window x:Class="VisualClient.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Controls ="clr-namespace:VisualClient.View.controls"
xmlns:vm ="clr-namespace:VisualClient.ViewModel"
xmlns:local="clr-namespace:VisualClient.View"
mc:Ignorable="d"
Title="Main Window" Height="350" Width="525" MinWidth="900" MinHeight="600">
<WrapPanel >
<Button Name ="bu" Content="Click_Me" Click="bu_Click"/>
<Grid Name="myGrid">
<Controls:NewMazeGrid Order="{Binding MySingleplay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</WrapPanel>
</Window>
I get this error on the binding line: Value cannot be null.
To sum:
It initialize fine the window in the ctor, but when the property changes it does not get into the Order property setter. therefor my grid never changes.
What should be the right syntax for binding in this case? how do I bind it to the right property?
Folders hierarchy explorer
WPF may not call the CLR wrapper of a dependency property, but just directly call the GetValue and SetValue methods of the underlying DependencyObject. This is why there should not be any logic except the GetValue and SetValue calls.
This is explained in XAML Loading and Dependency Properties:
Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
Similarly, other aspects of the XAML processor that obtain property
values from XAML processing also use GetValue rather than using the
wrapper. Therefore, you should also avoid any additional
implementation in the get definition beyond the GetValue call.
To get notified about property value changes, you can register a PropertyChangedCallback by property metadata. Note also that there is a naming convention for DependencyProperty fields. Yours should be called OrderProperty:
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register(
"Order", typeof(string), typeof(NewMazeGrid),
new PropertyMetadata(OnOrderChanged));
public string Order
{
get { return (string)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
private static void OnOrderChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((NewMazeGrid)obj).myMaze.setPresentation((string)e.NewValue);
}
Besides that, you must not set
DataContext = this;
in the constructor of NewMazeGrid. This effectively prevents inheriting the DataContext from the parent window, so that {Binding MySingleplay} won't work. Except under special circumstances you should never explicitly set a UserControl's DataContext.
So, remove the DataContext assignment from the constructor:
public NewMazeGrid()
{
myMaze = new MazePresentation();
InitializeComponent();
lst.ItemsSource = myMaze.MazePuzzleLists;
}
That said, there is also no need to set UpdateSourceTrigger=PropertyChanged on a one-way binding. It only has an effect in two-way (or one-way-to-source) bindings:
<Controls:NewMazeGrid Order="{Binding MySingleplay}"/>

wpf usercontrol with exposed commands in more than one place within application

I obviously don't get this somewhere.
I have created a UserControl, the bare bones of which is:
private readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register("SaveCommand", typeof(ICommand),
typeof(ctlToolbarEdit));
private readonly DependencyProperty IsSaveEnabledProperty =
DependencyProperty.Register("IsSaveEnabled", typeof(bool),
typeof(ctlToolbarEdit), new PropertyMetadata(
new PropertyChangedCallback(OnIsSaveEnabledChanged)));
public ctlToolbarEdit()
{
InitializeComponent();
}
public bool IsSaveEnabled
{
get { return (bool)GetValue(IsSaveEnabledProperty); }
set { SetValue(IsSaveEnabledProperty, value); }
}
public static void OnIsSaveEnabledChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((ctlToolbarEdit)d).cmdSave.IsEnabled = (bool)e.NewValue;
}
#region Command Handlers
public ICommand SaveCommand
{
get { return (ICommand)GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
private void cmdSave_Click(object sender, RoutedEventArgs e)
{
if (SaveCommand != null)
SaveCommand.Execute(null);
}
#endregion
Excellent. You can see what I am doing ... handling the click event of the button, and basically firing up the command.
The form (lets call that Form1 for the time being ... but note that this is actually a UserControl: common practice, I believe, in MVVM) that is hosting the control has the following line:
<ctl:ctlToolbarEdit HorizontalAlignment="Right" Grid.Row="1"
SaveCommand="{Binding Save}" IsSaveEnabled="{Binding IsValid}" />
This works great. I have an ICommand in my ViewModel called 'Save' and the ViewModel is correctly presenting the IsValid property.
So far so very good.
Now I want to have my new usercontrol also on Form2 (which is also a usercontrol - common practice, I believe, on MVVM). As it happens, Form1 and Form2 are on the screen at the same time.
It compiles, but I get a runtime exception:
'SaveCommand' property was already registered by 'ctlToolbarEdit'."
... leading me to believe that I don't get 'commands' at all.
Why can I not use my usercontrol in more than one place?
If I cannot, what would you suggest is another way to do this?
Very frustrating!
Thanks for any help.
Try making your dependency properties static. Otherwise it is getting re-registered every time you instantiate a new control. Your usage of the MVVM commands looks good otherwise and sounds like you have a good grasp on it.

INotifyPropertyChanged binding not updating as expected

So here is the problem I'm beating my head against: I have a custom user control that exposes two dependency properties that are bound to my ViewModel. In my ViewModel I have an instance of a class that holds multiple properties that express values that relate to the user control as well as to items that control manipulates. Here's a bit of sample code to explain it visually so here is a simple sample of my control, it's a Slider that is combined with a checkbox that allows the user to lock the slider.
<custom:SliderControl IsLocked="{Binding Path=CustomClass.IsLocked, Mode=TwoWay}" SliderValue="{Binding Path=CustomClass.Value, Mode=TwoWay}" />
IsLocked and SliderValue are dependency properties that effectively manipulate the checkbox and slider that are contained in the custom control. All of the control functions work as intended, except for the bindings to the class I've defined. If I create individual properties, as in one int property and one bool property the bindings work as intended. However I have five sliders, and each slider in my actual code has five properties that tie in to them. I'm trying to eliminate code duplication by creating a class to hold these properties in a reusable object shrinking my 25 properties down to 5 class instances.
My CustomClass inherits ObservableObject and has a bool property and int property named IsLocked and SliderValue respectively. For more visual aids here is what it looks like:
public class CustomClass : ObservableObject
{
public const string SliderValuePropertyName = "SliderValue";
private int _sliderValue= 0;
public int SliderValue
{
get
{
return _sliderValue;
}
set
{
if (_sliderValue== value)
{
return;
}
_sliderValue= value;
RaisePropertyChanged(SliderValuePropertyName );
}
}
public const string IsCheckedPropertyName = "IsChecked";
private bool _isChecked = false;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked == value)
{
return;
}
_isChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
The ViewModel property is very similar and looks like this, an new instance of the class is created when the ViewModel loads:
public const string SliderOnePropertyName = "SliderOne";
private CustomClass _sliderOne;
public CustomClass SliderOne
{
get
{
return _sliderOne;
}
set
{
if (_sliderOne== value)
{
return;
}
_sliderOne= value;
RaisePropertyChanged(SliderOnePropertyName );
}
}
Why won't the updating of the dependency property that is bound to the property in the class update properly? Is it because you can't properly update the class instance property by itself and instead have to update the entire class instance whenever changes occur? Or do I need to further customize the setter in this ViewModel property? As it sits now changing the slider value or checkbox never hits the bound property at all and nothing errors out when debugging.
EDIT: I've also surrounded the control in a Border and set the Border UIElement's DataContext to that of the class and then subsequently applied the more simple path binding to the underlying custom control. This however did not have any effect on my problem.
I'm a homegrown programmer so I often miss things when putting code together and I'm guessing this is the case here, unless what I'm trying just won't work.
Any help would be greatly appreciated.
EDIT: So I've been toying around with using a custom event that will let me know when the specific property of the custom control changes and then having that event wired up in my ViewModel to update the existing class. This works but still creates code duplication as now I have to have 10 events, 2 events per control, one to check for when the value of the slider changes and the other to detect when the checkbox IsChecked value changes. This code duplication exists since you can't route multiple command parameters (like a simple string identifier for which slider is being manipulated as well as the value you want to use in the code). This limitation means I can't just use 2 events that differentiate between which control is undergoing changes within the defined method as exposing the physical control to the ViewModel breaks the MVVM pattern. Using a class as the datacontext for the user control made it so I didn't care what control was being manipulated as they each had their own class instance. Using events this unravels the MVVM pattern as now I need to know which of the five controls is being manipulated by the user.
It can't be this hard to use a class in property bindings. I have to be missing something remedial.
here is a full example:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
public SliderValues slv { get; private set; }
public ViewModel()
{
slv = new SliderValues();
}
}
public class SliderValues : INotifyPropertyChanged
{
bool _isLocked = false;
public bool IsLocked
{
get { return _isLocked; }
set
{
_isLocked = value;
OnPropertyChanged("IsLocked");
}
}
int _theValue = 5;
public int TheValue
{
get { return _theValue; }
set
{
_theValue = value;
OnPropertyChanged("TheValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Now the xaml:
<UserControl x:Class="TestBindings.MainPage"
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"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Slider Height="23" HorizontalAlignment="Left" Margin="114,138,0,0" Name="slider1" VerticalAlignment="Top" Width="100"
DataContext="{Binding slv}" Value="{Binding TheValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
May be there is just a syntactical error. Try this
{Binding Path=CustomClass.IsLocked, Mode=TwoWay}
Try this...<custom:SliderControl DataContext="{Binding CustomClass}" IsLocked="{Binding IsLocked, Mode=TwoWay}" SliderValue="{Binding Value, Mode=TwoWay}" />

Categories