Dynamically Generate simple Views from ViewModels in WPF? - c#

I'm slowly learning WPF using this article and other resources.
I am focusing on the application logic - defining the model + viewModel, and creating commands that operate on these. I have not yet looked at the view and the .xaml format.
While I am working on the logic, I want to have a view that can render any viewModel I bind to it. The view should
Render any public string properties as text boxes, and bind the text box to the property
Render the name of the property as a label.
Render any public 'Command' property as a button, and bind the button to the command (perhaps only if the command takes no arguments?)
Is something like this possible while maintaing the MVVM design pattern? If so, how would I achieve it? Also, the article suggests to avoid using .xaml codebehind - can this view be implemented in pure xaml?

I don't think it is possible in XAML only. If you want to generate your views in runtime then you have to just use reflection over your ViewModels and generate controls accordingly. If you want to generate views at compile time then you can generate xaml files from your ViewModels at build time with some template engine (like T4 or string template) or CodeDom. Or you can go further and have some metadata format (or even DSL) from which you will generate both models and views and so on. It is up to your app needs.
And also in MVVM code-behind is Ok for visual logic and binding to model/viewmodel that can't be done in XAML only.

I'm not sure this is an appropriate use for a "pure MVVM" approach, certainly not everything is going to be achieved simply by binding. And I'd just throw away the idea of avoiding using code-behind for your "view" here, this is an inherently programmatic task. The one thing you should stick to is giving the ViewModel no knowledge of the view, so that when you replace it with the "real thing" there is no work to do.
But certainly seems reasonable thing to do; it almost sounds more like a debugging visualiser - you may be able to leverage an existing tool for this.
(If you did want to do this in mostly XAML with standard ItemsControls and templates you might write a converter to expose properties of your ViewModel by reflection in some form that you can bind to, a collection of wrapper objects with exposed metadata, but I think ensuring that the properties exposed are properly bindable would be more work than it's worth)

I'm halfway through implementing this now, I hope the following code will help anyone else trying to do this. It might be fun to turn into a more robust library.
AbstractView.xaml:
<UserControl x:Class="MyApplication.View.AbstractView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Name="container">
</StackPanel>
</UserControl>
AbstractView.xaml.cs:
public partial class AbstractView : UserControl
{
public AbstractView()
{
InitializeComponent();
DataContextChanged += Changed;
}
void Changed(object sender, DependencyPropertyChangedEventArgs e)
{
object ob = e.NewValue;
var props = ob.GetType().GetProperties();
List<UIElement> uies = new List<UIElement>();
foreach (var prop in props)
{
if (prop.PropertyType == typeof(String))
uies.Add(makeStringProperty(prop));
else if (prop.PropertyType == typeof(int))
uies.Add(makeIntProperty(prop));
else if (prop.PropertyType == typeof(bool))
uies.Add(makeBoolProperty(prop));
else if (prop.PropertyType == typeof(ICommand))
uies.Add(makeCommandProperty(prop));
else
{
}
}
StackPanel st = new StackPanel();
st.Orientation = Orientation.Horizontal;
st.HorizontalAlignment = HorizontalAlignment.Center;
st.Margin = new Thickness(0, 20, 0, 0);
foreach (var uie in uies) {
if (uie is Button)
st.Children.Add(uie);
else
container.Children.Add(uie);
}
if (st.Children.Count > 0)
container.Children.Add(st);
}
UIElement makeCommandProperty(PropertyInfo prop)
{
var btn = new Button();
btn.Content = prop.Name;
var bn = new Binding(prop.Name);
btn.SetBinding(Button.CommandProperty, bn);
return btn;
}
UIElement makeBoolProperty(PropertyInfo prop)
{
CheckBox bx = new CheckBox();
bx.SetBinding(CheckBox.IsCheckedProperty, getBinding(prop));
if (!prop.CanWrite)
bx.IsEnabled = false;
return makeUniformGrid(bx, prop);
}
UIElement makeStringProperty(PropertyInfo prop)
{
TextBox bx = new TextBox();
bx.SetBinding(TextBox.TextProperty, getBinding(prop));
if (!prop.CanWrite)
bx.IsEnabled = false;
return makeUniformGrid(bx, prop);
}
UIElement makeIntProperty(PropertyInfo prop)
{
TextBlock bl = new TextBlock();
bl.SetBinding(TextBlock.TextProperty, getBinding(prop));
return makeUniformGrid(bl, prop);
}
UIElement makeUniformGrid(UIElement ctrl, PropertyInfo prop)
{
Label lb = new Label();
lb.Content = prop.Name;
UniformGrid u = new UniformGrid();
u.Rows = 1;
u.Columns = 2;
u.Children.Add(lb);
u.Children.Add(ctrl);
return u;
}
Binding getBinding(PropertyInfo prop)
{
var bn = new Binding(prop.Name);
if (prop.CanRead && prop.CanWrite)
bn.Mode = BindingMode.TwoWay;
else if (prop.CanRead)
bn.Mode = BindingMode.OneWay;
else if (prop.CanWrite)
bn.Mode = BindingMode.OneWayToSource;
return bn;
}
}

Pointer: Generate a dynamic DataTemplate as a string tied to the specific VM (Target). Parse it via XamlReader. Plonk it into your app resources in code.
Just an idea.. run with it.. Should be done by some type other than the View or the ViewModel.

Related

Issue with property not passing back value to text box on property change

I am working on a WPF application and i have a textbox bound (bidirectionally) to a property in my view model.
I am trying to prevent a user from typing more than 100 characters into this textbox (this is the max the database will store) so i have written this.
public abstract class AppBaseViewModel : ViewModelBase
{
private String _text;
public String Text
{
get { return _text; }
set
{
_text = CheckTextLength(value, _text);
OnPropertyChanged("Text");
}
}
private string CheckTextLength(string value, string text)
{
if (value.Length < 100)
{
return value;
}
else
{
return text;
}
}
}
All this code seems to do is save the first 100 characters to the field but it still allows the user to carry on typing past 100 characters... i would guess it is because the field value isn't being passed back to the textbox.
I don't understand why this doesn't work as i did something similar using MVVM Light's RaisePropertyChange() in a different application.
It is worth noting that i am unable to access the designer for the textbox so cannot set the .Net textbox property for max length.
Edit: Just for clarification i cannot view or edit the xaml as some are suggesting as i do not have access to the XAML file (i know, it's stupid). All the bindings we use are two way by default
Have you tried with TextBox.MaxLength ?
<TextBox MaxLength="100"/>
Gets or sets the maximum number of characters that can be manually entered into the text box.
If no access to the XAML, eventually get access to the XAML instead of parsing and verifying lengths of arrays and use substrings here and there. At least that's what i would do for this simple issue or talk to the designer to add that small piece of code.
Update 1
public static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
Go and get that child and set its MaxLength. This is just a slight modification on the View so it will not affect the MVVM pattern.
OK. I'm not at all sure that I'm proud of this, but am presenting it as an alternative.
You can change the UpdateSourceTrigger of the TextBox's Text property by applying a universal Style to all of the TextBoxes. This is only going to be practical in pretty weird arrangements, but the question is a little unusual in itself.
XAML codebehind:
//I'm using MVVM Light here - you need to be able to find an instance
//of your AppBaseViewModel somehow.
private ViewModelLocator _locator;
//View codebehind constructor, may need to change names as appropriate
public AppBaseView()
{
InitializeComponent();
//MVVM Light again
_locator = new ViewModelLocator();
//Create the binding
Binding binding = new Binding();
//Source = The instance of your ViewModel
binding.Source = _locator.AppBaseViewModel ;
binding.Path = new PropertyPath("Text");
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
//Create a Style with no Key - this will apply to *all* TextBoxes
//without their own explicit Style set.
Style style = new Style(typeof(TextBox));
style.Setters.Add(new Setter(TextBox.TextProperty, binding));
//Add the Style to the XAML's Resources:
Resources.Add(typeof(TextBox), style);
}
The view won't listen to the PropertyChanged notification if it's currently trying to change the property itself.
The only thing that comes to mind is launching an extra delayed PropertyChanged notification when you detect the constraint is not met...
private string CheckTextLength(string value, string text)
{
if (value.Length < 100)
{
return value;
}
else
{
MyDispatcher.BeginInvoke(new Action(() =>
OnPropertyChanged("Text")),
DispatcherPriority.Loaded);
return text;
}
}
Can't try the code, so sorry if it doesn't build righ away. MyDispatcher could be your Application.Current.Dispatcher, for instance.
The xaml view /the binding is only updated when the textbox has lost focus. if the text entered is <100 then the value is set otherwise _text is set. this means that initially _text has no value so null will be set upon the if statement being false. i also suggest yo use RaisePropertyChanged(); and when used within the property itself no parameter is needed.

Bind XAML Map control to Code behind

EXPLANATION:
I can't use code behind because I'm using some interfaces who hare injected with Ninject, I can't just do service = new service() because there are a lot more dependencies behind that one. As for the question about the type name as a converter parameter, I found this solution best for binding my model to my UI. I use this for all of my modules and is working great. Just my Map control is not working at all.
Atm I'm trying to include a Microsoft.Phone.Maps.Controls.Map control in my Windows Phone 8 app. But I can't seem to get it working.
Because of Dependency injection and stuff I can't use the code behind of my xaml page. So I made myself a MapController who is linked to the Xaml as a DataContext. But I got some problems with it.
XAML:
<phone:PhoneApplicationPage
...
xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"
DataContext="{Binding Source={StaticResource DynamicLocator}, Converter={StaticResource IndexConverter}, ConverterParameter='BaseCpr.Plugins.ShopLocator.Model.MapController,BaseCpr.Plugins'}">
<Grid>
<maps:Map DataContext="{Binding MapControl, Mode=TwoWay}">
</maps:Map>
</Grid>
</phone:PhoneApplicationPage>
MapController:
public MapController(IService Service) {
//Service is used here a lot
MapControl = new Map() {
CartographicMode = MapCartographicMode.Road,
Heading = 0,
Pitch = 0,
ZoomLevel = 7,
Center = new GeoCoordinate(50.50, 4.000)
};
}
private Map mapControl;
public Map MapControl {
get { return mapControl; }
set {
if (mapControl != value) {
mapControl = value;
RaisePropertyChanged(() => MapControl);
}
}
}
The link between the XAML and my MapController is working (tested it with simple boolean bindings). And when I'm debugging the getter of MapControl get called. But my map on my screen is still at the default view. Can someone help me out what I'm doing wrong?
Thanks
EDIT 2: (some more info)
In my MapController I have the following function as well:
public async void getPhoneLocation() {
try
{
//getting location and put it in geoposition
var cord = new GeoCoordinate(geoposition.Coordinate.Latitude, geoposition.Coordinate.Longitude);
MapControl.SetView(cord, 16, 0, 0, MapAnimationKind.Parabolic);
CreateAndAddUserMarker(cord);
}
catch (Exception ex)
{
//TODO
}
}
This is why I can't just bind all Map properties of my XAML map control. I need to call the SetView() function as well. I also need to add Layers to my map to add pins on the map. getPhoneLocation() is called in the MapController constructor.
To solve my problem by making a MapBehavior and bind a ViewModel property to a Dependency Property from the MapBehavior.
That way, when my Location get's updated it will fire the Dependency Property PropertyChangedCallback.
public DependencyProperty LocationProperty = DependencyProperty.Register(
"Location", typeof(GeoCoordinate), typeof(MapBehavior), new PropertyMetadata(null, (sender, args) =>
{
if (args.NewValue != null && args.NewValue != args.OldValue)
{
var sendMap = ((MapBehavior)sender);
sendMap.AssociatedObject.SetView((GeoCoordinate)args.NewValue, 14, 0, 0, MapAnimationKind.Parabolic);
}
}));
In sender is the Map control, that way the SetView() method on your MapControl can be used :)
It took me a while to get this solution so I wanted to share it anyway

MVVM - UI related code in View model - true separation of concerns

I started implementing a MVVM design pattern in an existing WPF c# application. I am completely new and have never used design patterns or dependency injection before. I was looking at the frameworks already available and have adopted MVVM light. I moved the logic from the view to the viewmodel. I have lot of code in the PopulateTestMenu which is related to UI in the view model. It also has calls to the event handlers. How do I take care of this?
In the XAML I have:
<Window DataContext="{Binding Main, Source={StaticResource Locator}}">
<Menu>
<MenuItem Header="Load All History..." Command="{Binding LoadAllHistory}">
In the MainViewModel class I have:
public ICommand LoadAllHistory { get; private set; }
public MainViewModel()
{
LoadAllHistory = new RelayCommand(() => LoadHistoryExecute(), () => true);
}
The code that I moved from my view to the viewmodel:
private void LoadHistoryExecute()
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Test History File (*.xml)|*.xml";
ofd.Title = "Open Test History";
ofd.Multiselect = true;
if (ofd.ShowDialog() == true)
{
ThreadPool.QueueUserWorkItem(LoadTestHistoryCallback, ofd.FileNames);
}
}
catch
{
//some code
}
}
private void LoadTestHistoryCallback(object state)
{
try
{
string[] fileNames = (string[])state;
foreach (string fileName in fileNames)
{
bool success = MyApp.Instance.ParseTestHistory(fileName);
string status = success
? String.Format("'{0}' loaded successfully.",
System.IO.Path.GetFileName(fileName))
: String.Format("Failed to load history from '{0}'.",
System.IO.Path.GetFileName(fileName));
Dispatcher.CurrentDispatcher.DynamicInvoke(delegate()
{
Status = status;
});
PopulateTestMenu(new SortedList<int, int>());
}
}
catch
{
//some code
}
}
private void PopulateTestMenu(SortedList<int, int> indexes)
{
try
{
_testMenuMutex.WaitOne();
//Populate the Tests menu with the list of tests.
Dispatcher.CurrentDispatcher.DynamicInvoke(delegate()
{
menuTests.Items.Clear();
var checkEventHandler = new RoutedEventHandler(testMenuItem_Checked);
bool added = false;
if (MyApp.Instance.TestHistory != null &&
MyApp.Instance.TestHistory.Count > 0)
{
List<ushort> subIds = new
List<ushort>MyApp.Instance.TestHistory.Keys);
foreach (ushort subId in subIds)
{
MenuItem menuItem = null;
menuItem = new MenuItem();
menuItem.Header = subId.ToString().PadLeft(5, '0');**
MenuItem none = new MenuItem();
none.Header = "None";
none.IsCheckable = true;
none.IsChecked = true;
none.Checked += checkEventHandler;
none.Unchecked += checkEventHandler;
menuItem.Items.Add(none);
if (MyApp.Instance.TestHistory != null &&
MyApp.Instance.TestHistory.ContainsKey(subId))
{
var tests = MyApp.Instance.TestHistory[subId];
if (tests != null)
{
foreach (Test t in tests)
{
MenuItem item = new MenuItem();
item.IsCheckable = true;
string description = t.Description.Replace("\n",
"\n".PadRight(34, ' '));
string header = abc;
item.Header = header;
item.DataContext = t;
item.Checked += checkEventHandler;
item.Unchecked += checkEventHandler;
menuItem.Items.Add(item);
}
if (tests.Count > 0)
{
menuTests.Items.Add(menuItem);
added = true;
}
}
}
// Carry over the previous selection.
if (indexes.ContainsKey(subId) && indexes[subId] > -1)
{ ((MenuItem)menuItem.Items[indexes[subId]]).IsChecked =
true;
}
}
}
I am still trying to figure out what you are asking =)...
But you are mixing up some things... Remember one of the core concepts of MVVM is to make the viewmodel testable and remove all view related code off from the viewmodel. So no dependencies to WPF at all. So MenuItem looks like a WPF MenuItem and should not be in your ViewModel.
Instead you could consider to make a MenuItemViewModel which binds to the MenuItem in the View. And it I could see an ObservableCollection<MenuItemViewModel> TestMenu instead of your sorted list.
In your method LoadTestHistoryCallback you would instanciate (could be done via DI) the MenuItemViewModel and add it to the TestMenu Collection. The MenuItemViewModel could have status property which could be assigned from outside or internaly. (It can also have some additional logic, hey its a viewmodel).
In the View you could then bind it to a list with a template representing the MenuItem via DataBinding.
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />
So remember ViewModel can also contain ViewModels or collections of viewmodel.
Use the rich databinding api from WPF.
Work with bindable Properties like ObservebaleCollections or Properties that are extended with PropertyChanged notification.
HTH
P.S: You can then have a click ICommand in the MenuItemViewModel and execute actions or better use the EventAggregator or Messenger to notify other ViewModels ...(but that's a story for another question =)... )
You have applied the theory of MVVM correctly by moving that code to the ViewModel however just keep in mind that the View should only provide the "structure" of the display.
What is displayed is provided by the model in the ViewModel.
With that in mind separate out the menu parts from the ViewModel method and put them in the View, but leave the Test object creation parts (Binding ViewModel objects to View structure is what it's about).
Within your PopulateTestMenu method the menus and menu structure need to be specified in the View while the data populating them needs to be created and formatted in the ViewModel.
In the View you will bind the appropriate object parts to the menu structure, and the ViewModel will automatically fill it in with the model objects when the model is bound to the view.
Looking at the code, it appears that your Test object is your ViewModel, and the Menu and MenuItem structure needs to be created in the View, then you specify the binding of the specific properties of the Test object to the specific structure parts of the Menu within the View.

How to handle purely view-related commands?

There is my WPF window in which I placed an ordinary textbox which I would liked to be focused when Ctrl+F is pressed.
As I would like to keep it MVVM-like as much as possible, I use InputBindings on the window to bind that input event to a Command provided in the ViewModel (is that already breaking MVVM pattern because the whole action is only meant to be part of the view? I guess not, as the Command is an object to bind to).
How can the ViewModel communicate with the view to focus the textbox? I read that this already breaks the MVVM pattern, but sometimes simply is necessary as otherwise impossible. However, setting the focus in the ViewModel itself would be totally breaking the MVVM pattern.
I orginally intended to bind the current focused control in the window to a property of the ViewModel but it is quite difficult to even determine the currently focused element in WPF (that always makes me question if it really is the right way to do so).
In cases like this there's just no way to not 'break' pure MVVM. Then again, I'd hardly call it breaking anything. I don't think any decently sized MVVM app out there is 'pure'. So, just stop caring too much about breaking whatever pattern you use and implement a solution instead.
There are at least two ways here:
simply do everything in code behind in the View: check if the key is pressed, if so, set focus. It won't get any simpler than that and you could argue the VM has nothing to do with something that's really all View related
else there is obviously going to have to be some communication between VM and View. And this makes everything more complicated: suppose you use the InputBinding, your command can set a boolean property and then the View can bind to it in turn to set focus. That binding can be done like in Sheridan's answer with an attached property.
Generally, when we want to use any UI event while adhering to the MVVM methodology, we create an Attached Property. As I just answered this very same question yesterday, I would advise you to take a look at the how to set focus to a wpf control using mvvm post here on StackOverflow for a full working code example.
The only difference from that question to yours is that you want to focus the element on a key press... I'm going to assume that you know how to do that part, but if you can't, just let me know and I'll give you an example of that too.
when using mvvm and further when you define a viewmodel with:
a viewmodel should not know/reference the view
then you cant set focus through the viewmodel.
but what i do in mvvm is the following in the viewmodel:
set the focus to the element which is bind to the viewmodel property
for this i create a behavior which simply walk through all control in the visual tree and look for the binding expressions path. and if i find a path expression then simply focus the uielement.
EDIT:
xaml usage
<UserControl>
<i:Interaction.Behaviors>
<Behaviors:OnLoadedSetFocusToBindingBehavior BindingName="MyFirstPropertyIWantToFocus" SetFocusToBindingPath="{Binding Path=FocusToBindingPath, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</UserControl>
viemodel in any method
this.FocusToBindingPath = "MyPropertyIWantToFocus";
behavior
public class SetFocusToBindingBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty SetFocusToBindingPathProperty =
DependencyProperty.Register("SetFocusToBindingPath", typeof(string), typeof(SetFocusToBindingBehavior ), new FrameworkPropertyMetadata(SetFocusToBindingPathPropertyChanged));
public string SetFocusToBindingPath
{
get { return (string)GetValue(SetFocusToBindingPathProperty); }
set { SetValue(SetFocusToBindingPathProperty, value); }
}
private static void SetFocusToBindingPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as SetFocusToBindingBehavior;
var bindingpath = (e.NewValue as string) ?? string.Empty;
if (behavior == null || string.IsNullOrWhiteSpace(bindingpath))
return;
behavior.SetFocusTo(behavior.AssociatedObject, bindingpath);
//wenn alles vorbei ist dann binding path zurücksetzen auf string.empty,
//ansonsten springt PropertyChangedCallback nicht mehr an wenn wieder zum gleichen Propertyname der Focus gesetzt werden soll
behavior.SetFocusToBindingPath = string.Empty;
}
private void SetFocusTo(DependencyObject obj, string bindingpath)
{
if (string.IsNullOrWhiteSpace(bindingpath))
return;
var ctrl = CheckForBinding(obj, bindingpath);
if (ctrl == null || !(ctrl is IInputElement))
return;
var iie = (IInputElement) ctrl;
ctrl.Dispatcher.BeginInvoke((Action)(() =>
{
if (!iie.Focus())
{
//zb. bei IsEditable=true Comboboxen funzt .Focus() nicht, daher Keyboard.Focus probieren
Keyboard.Focus(iie);
if (!iie.IsKeyboardFocusWithin)
{
Debug.WriteLine("Focus konnte nicht auf Bindingpath: " + bindingpath + " gesetzt werden.");
var tNext = new TraversalRequest(FocusNavigationDirection.Next);
var uie = iie as UIElement;
if (uie != null)
{
uie.MoveFocus(tNext);
}
}
}
}), DispatcherPriority.Background);
}
public string BindingName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectLoaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObjectLoaded;
}
private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
SetFocusTo(AssociatedObject, this.BindingName);
}
private DependencyObject CheckForBinding(DependencyObject obj, string bindingpath)
{
var properties = TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.All) });
if (obj is IInputElement && ((IInputElement) obj).Focusable)
{
foreach (PropertyDescriptor property in properties)
{
var prop = DependencyPropertyDescriptor.FromProperty(property);
if (prop == null) continue;
var ex = BindingOperations.GetBindingExpression(obj, prop.DependencyProperty);
if (ex == null) continue;
if (ex.ParentBinding.Path.Path == bindingpath)
return obj;
}
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var result = CheckForBinding(VisualTreeHelper.GetChild(obj, i),bindingpath);
if (result != null)
return result;
}
return null;
}
}
(is that already breaking MVVM pattern because the whole action is
only meant to be part of the view? I guess not, as the Command is an
object to bind to)
The Command system in WPF was actually not designed around data-binding, but the UI -- using RoutedCommands, a single command would have different implementations based on the physical position in the UI structure of the element that called the command.
Commanding Overview
Your flow would be:
Ctrl+F is pressed
command event is raised and bubbles up
the event reaches the window, which has a CommandBinding to the command
event handler on the window focuses the text box
If the current element is inside a container that wants to handle the command differently, it will stop there before it reaches the window.
This is probably closer to what you want. It may make sense to involve the view model if there is some concept of an "active property" like in blindmeis's answer, but otherwise I think you would just end up with a redundant / circular flow of information e.g. key pressed -> view informs viewmodel of keypress -> viewmodel responds by informing view of keypress.
After a few days of getting a better grip on all of this, considering and evaluating all options, I finally found a way to work it out. I add a command binding in my window markup:
<Window.InputBindings>
<KeyBinding Command="{Binding Focus}" CommandParameter="{Binding ElementName=SearchBox}" Gesture="CTRL+F" />
</Window.InputBindings>
The command in my ViewModel (I cut the class down to what matters in this case):
class Overview : Base
{
public Command.FocusUIElement Focus
{
get;
private set;
}
public Overview( )
{
this.Focus = new Command.FocusUIElement();
}
}
And finally the command itself:
class FocusUIElement : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute ( object parameter )
{
return true;
}
public void Execute ( object parameter )
{
System.Windows.UIElement UIElement = ( System.Windows.UIElement ) parameter;
UIElement.Focus();
}
}
This might not be straigt MVVM - but stijn's answer has a good point:
So, just stop caring too much about breaking whatever pattern you use
and implement a solution instead.
Normally I take care of keeping stuff organised by conventions, especially when I am still new to something, but I do not see anything wrong regarding this.

Copying a TabItem with an MVVM structure

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);

Categories