Binding WPF Grid to a model - c#

I have a MVVM application which has a WPF Grid which contains other embedded WPF Grids and at the same time, each of them contain some fields (WPF TextBlocks).
Very simplified example - View:
<Grid>
<Grid>
// Row definitions
// Colum definitions
<TextBlock Grid.Row="3" Grid.Column="0"
Text="{Binding Path=SomeField1}" />
<Grid>
<Grid>
// Row definitions
// Colum definitions
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding Path=SomeField2}" />
<Grid>
</Grid>
Each of these TextBlocks are bound to a string properties defined in view model.
View model (It implements INotifyPropertyChanged):
private string _someField1;
public string SomeField1
{
get return _someField1;
set
{
if (_someField1 == value) return;
_someField1 = value;
OnPropertyChanged("SomeField1");
}
}
private string _someField2;
public string SomeField2
{
get return _someField2;
set
{
if (_someField2 == value) return;
_someField2 = value;
OnPropertyChanged("SomeField2");
}
}
Then I have a model, I mean, a class with some public properties that is filled in by one process once data is obtained from a device. This class contains exactly the same properties as those defined in the view model.
Model:
public class MyModel
{
private string _someField1;
public string SomeField1
{
get return _someField1;
set
{
if (_someField1 == value) return;
_someField1 = value;
}
}
private string _someField2;
public string SomeField2
{
get return _someField2;
set
{
if (_someField2 == value) return;
_someField2 = value;
}
}
}
Later from view model I extract the data from this class (model), and I assign the values of those properties to the matching properties in view model. Finally, since view is bound to these properties, then view is correctly updated with values as below example.
View model method which extracts data received:
private void DataReceived(MyModel data)
{
this.SomeField1= data.SomeField1;
this.SomeField2= data.SomeField2;
}
The problem is that I have to define twice the properties, in view model and model. So I want to avoid this, I would like to bind Textblocks directly to properties in model and not defined the properties in view model to avoid redundant code. Or for example, is there any easy way to bind my model (MyModel) to the outer main grid and then textboxes bound to the properties in the view model (similar when bound itemsource in datagrid)?

I would suggest a generic view model:
public class BaseViewModel<TModel>
{
public TModel Model
{
get;
private set;
}
public BaseViewModel(TModel model)
{
this.Model = model;
}
}
Then you can bind to it:
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding Path=Model.SomeField1}" />

I was thinking if below it is ok and respects MVVM pattern. I have thought it after seeing solution proposed by c0d3b34n. I think it is simpler and no need to do interfaces and generic view model. I have checked and it works:
Declare a property in view model:
private MyModel _model;
public MyModel Model
{
get { return _model; }
set
{
_model = value;
OnPropertyChanged("Model");
}
}
Then in the view:
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding Path=Model.SomeField1}" />
... and the same for the rest of TextBlocks.
Finally:
private void DataReceived(MyModel data)
{
this.Model = data;
}
But as said by BionicCode in comments, this solution breaks MVVM pattern.

Related

Dynamic load of DataTemplates

How can I provide data templates dynamically from code?
I have view model MainWindowViewModel that I bind to MainWindow. It looks like this:
public class MainWindowViewModel : ViewModelBase
{
public int Progress
{
get => _progress;
set => RaiseAndSetIfChanged(ref _progress, value);
}
public ViewModelBase? Content
{
get => _content;
set => RaiseAndSetIfChanged(ref _content, value);
}
}
then in the MainWindow I chose DataTemplate based on a type tied to my MainWindowViewModel
<ContentControl Name="Header" Content="{Binding Content}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:DefaultViewModel}">
<local:DefaultView></local:DefaultView>
</DataTemplate>
</ContentControl.ContentTemplate>
It is currently only a single DataTemplate but I would like to load it from different places so I'm trying to replace it with dynamically generated list of DataTemplates as in the future, some control I will need to load from external file provided by user.
How to achieve that?
You can create your own IDataTemplate implementation and forward calls to the actual template dynamically. E. g. ViewLocator from Avalonia MVVM template looks like this:
public class ViewLocator : IDataTemplate
{
public IControl Build(object data)
{
var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
else
{
return new TextBlock { Text = "Not Found: " + name };
}
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}

Prism 5.0 wrap Model Attributes in ViewModel

Hello I am relatively new with Prism but here is my question:
I am currently using Prism 5 with WPF. I have created a model, viewmodel and view. The View gets updated when an attribute from model changes. My problem is: when I want to process the Attribute from the model(for example another attribute name), the view does not get updated. Heres my Code. I'll be very thnakful if you could help me.
When RunTinting in the model Changes, the view does not get updated.
Model
public class MyModel :BindableBase
{
private Boolean _RunTinting;
public Boolean RunTinting
{
get { return _RunTinting; }
set { SetProperty(ref _RunTinting, value); }
}
Viewmodel
public class MainWindowViewModel : BindableBase
{
private MyModel model;
public MyModel Model
{
get { return this.model; }
set { SetProperty(ref this.model, value); }
}
public MainWindowViewModel()
{
this.Model=new MyModel();
}
public Boolean RunTinting2
{
get { return this.model.RunTinting; }
set { SetProperty(ref this._RunTinting, value); }
}
}
XAML
<Label x:Name="label1_Copy11" Content="{Binding RunTinting2}" HorizontalAlignment="Left" Margin="366,320,0,0" VerticalAlignment="Top" Height="25" Width="85" >
If you set the model property dynamically the view should bind to this property instead of the view model property:
<Label x:Name="label1_Copy11" Content="{Binding Model.RunTinting}" HorizontalAlignment="Left" Margin="366,320,0,0" VerticalAlignment="Top" Height="25" Width="85" >
Because the view model won't raise any change notifications when a property of the model is set to a new value.
The other option is to set the model property through the wrapper property of the view model, i.e. instead of setting the model property directly you set the view model property. Make sure that you then set the model's property in the setter of the wrapper property and that you raise a PropertyChanged event for the view model property that the view binds to:
public class MainWindowViewModel : BindableBase
{
...
public Boolean RunTinting2
{
get { return this.model.RunTinting; }
set { this.model.RunTinting = value; OnPropertyChanged("RunTinting2"); }
}
}
<Label x:Name="label1_Copy11" Content="{Binding RunTinting2}" HorizontalAlignment="Left" Margin="366,320,0,0" VerticalAlignment="Top" Height="25" Width="85" >
this is how I solved this problem, thank you Rachel and Will.
ViewModel
public void InitModel()
{
Model.PropertyChanged += Model_PropertyChanged;
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "RunTinting")
{
OnPropertyChanged("RunTinting2");
}
}
#mm8, I tried your solution, but it didnt work... but thank you anyways for the time taken.
I do not know why my answer has negative ratings. It works and respect the mvvm model!

Adding/removing rows of controls

I have a panel and my idea is to have it populated by a stack panel containing two text boxes. When the user enters something in the left box, something should be generated in the right one, as follows.
<StackPanel Orientation="Horizontal">
<TextBox Name="Lefty" LostFocus="FillMyBuddy" />
<TextBox Name="Righty" LostFocus="FillMyBuddy" />
</StackPanel>
However, I'd like to add an option to add/remove rows and, since I wish not to limit myself to the number of such, I get a bit uncertain regarding the approach on two points.
Manipulating DOM (well, it's XAML/WPF but you see what I'm aiming at).
Event handling.
Is it a big no-no to programmatically affect the mark-up structure of the window? Or is it OK to add/remove panels during run-time?
What would the recommended way to be if I want the Lefty number 3 change stuff in Righty number 3? Anything more neat than checking the sender and pulling its siblings from the parent? I want to use a single event handler for any and all rows (knowing that the operations are always intra-row-wise).
You will want to follow MVVM, and have no code in your code-behind (programmatically affect the mark-up structure) files. The concept is easy when you grasp it, so learn it before you start writing your code.
In short, you are going to want to have a view model (something that implements INotifyPropertyChanged (INPC)) which holds your collection of items (which are going to be models, or view models in pure-MVVM). In "hybrid"-MVVM you could just have your models implement INPC.
Then, through the use of commands, you'd implement the logic to remove items from the list that its in. You can pass references, raise notification, using event bubbling, etc. (it's your preference) to have the item actually removed. In my case, I just passed a "manager" to the hybrid-model and held a reference to that. When the command is called (button is clicked), the model calls for the reference to remove itself from the list.
After you do that you define a DataTemplate to define what an "item" should look like one the View. You use a ItemsControl to show a collection of items, and bind to its ItemsSource so the collection of items are shown. Set your ItemsControl.ItemTemplate to the DataTemplate you created, and anything added to the collection bound to ItemsSource of the type defined in DataTemplate.DataType will render as you specify in the DataTemplate.
At the end of the day, you should learn about MVVM design, DataContext, INPC, Commands, Control types and their "main" properties, e.g. everything that inherits from ItemsControl has an ItemsSource property.
Here is a working example, where changing the original string, will reverse it and put it in the read-only right side text box:
MainWindow.xaml.cs (code-behind)
public partial class MainWindow : Window
{
StructureVm _struct = new StructureVm("Test");
public MainWindow()
{
InitializeComponent();
DataContext = _struct;
}
}
MainWindow.xaml (View)
<Window x:Class="DataTemplateWithCommands.MainWindow"
xmlns:local="clr-namespace:DataTemplateWithCommands"
Title="MainWindow" Height="350" Width="525" Background="Orange">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Model}"
x:Key="VmItem">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Original, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Encoded}"
IsReadOnly="True" />
<Button Content="X"
Command="{Binding RemoveMeCommand}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource VmItem}">
</ItemsControl>
</Grid>
</Window>
Interface (helpful for Dependency Injection)
public interface IStructureManager
{
bool RemoveItem(Model itemToRemove);
}
ViewModel
public class StructureVm : IStructureManager
{
private readonly ObservableCollection<Model> _items;
private readonly string _title;
public StructureVm(string title)
{
_title = title;
_items = new ObservableCollection<Model>
{
new Model(this, "12"),
new Model(this, "23"),
new Model(this, "34"),
new Model(this, "45"),
new Model(this, "56"),
new Model(this, "67"),
new Model(this, "78"),
new Model(this, "89"),
};
}}
public ObservableCollection<Model> Items
{
get
{
return _items;
}
}
public string Title
{
get
{
return _title;
}
}
public bool RemoveItem(Model itemToRemove)
{
return _items.Remove(itemToRemove);
}
}
Model (not pure-MVVM, pure MVVM models don't implement INPC, and don't have Command in them)
public class Model : INotifyPropertyChanged
{
private readonly RelayCommand _removeMe;
private string _original;
private string _encoded;
private readonly IStructureManager _manager;
public string Original
{
get
{
return _original;
}
set
{
_original = value;
Encoded = ReverseString(_original);
NotifyPropertyChanged();
}
}
public string Encoded
{
get
{
return _encoded;
}
set
{
_encoded = value;
NotifyPropertyChanged();
}
}
public ICommand RemoveMeCommand
{
get
{
return _removeMe;
}
}
public Model(IStructureManager manager, string original)
{
Original = original;
_manager = manager;
_removeMe = new RelayCommand(param => RemoveMe(), param => CanRemoveMe);
}
private void RemoveMe()
{
_manager.RemoveItem(this);
}
private bool CanRemoveMe
{
get
{
//Logic to enable/disable button
return true;
}
}
private string ReverseString(string s)
{
char[] arr = s.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand implementation
From here on out all you have to do is change the attributes of your controls to whatever you're happy with and call it good. The example might be ugly, but I'm leaving it as an exercise for you to figure out other properties/attributes of WPF controls.

Cannot tyoe in WPF/RadComboBox which is bound to data

I have multiple comboboxes on a WPF window. Each is populated from a ViewModel. I am trying to implement AutoComplete. I have tried using a WPF combobox and telerik combobox. I can't even begin to test whether my autocomplete functionality is workign or not because I cannot type in the combobox. i can only use backspace and spacebar. I have IsEditable set to true. Is there something very basic I am missing?
My xaml from one of the comboboxes
<DockPanel Style="{StaticResource DockPanelStyle}">
<Label Content="Model" DockPanel.Dock="Top"/>
<telerik:RadComboBox x:Name="cboModel" DockPanel.Dock="Bottom" Width="100" ItemsSource="{Binding Path=Models}"
ItemTemplate="{StaticResource ComboBoxCustomTemplate}" IsEditable="True" StaysOpenOnEdit="True"
telerik:TextSearch.TextPath="value"/>
</DockPanel>
The DockPanel above is inside a stackpanel which is inside a grid.
Here is the relevant code from my ViewModel
public void LoadModels()
{
try
{
List<CommonData.Model> model = factory.GetStaticModels();
foreach (CommonData.Model m in model)
{
Models.Add(new CommonData.Model()
{
value = m.value
});
}
}
catch (Exception ex)
{
//leaving this out
}
}
private List<CommonData.Model> _models = new List<CommonData.Model>();
public List<CommonData.Model> Models
{
get
{
return _models;
}
set
{
_models = value;
OnPropertyChanged("Models");
}
}
And finally, this is my Model class(not MVVM Model, the name of the class is Model)
[Serializable]
public class Model
{
private string models;
public string value
{
get;
set;
}
}
Any help/suggestions greatly appreciated. I am very new to WPF and I feel like I am missing something very basic but having spent a good part of 3-4 days on this, its becoming quite ridiculous now.
IsTextSearchEnabled = "True" for standard combobox

how can i get data from view

i use MVVM to built my project, now i have some troubles,when i click a button, i want get data from view to viewmodel, what should i do?
thanks
Bob
Bind that data to the view model and execute a command when the user clicks the button. The command and data are housed in the view model, so it has everything it needs.
public class YourViewModel : ViewModel
{
private readonly ICommand doSomethingCommand;
private string data;
public YourViewModel()
{
this.doSomethingCommand = new DelegateCommand(this.DoSomethingWithData);
}
public ICommand DoSomethingCommand
{
get { return this.doSomethingCommand; }
}
public string Data
{
get { return this.data; }
set
{
if (this.data != value)
{
this.data = value;
this.OnPropertyChanged(() => this.Data);
}
}
}
private void DoSomethingWithData(object state)
{
// do something with data here
}
}
XAML:
<TextBox Text="{Binding Data}"/>
<Button Command="{Binding DoSomethingWithData}"/>
For information on the various dependencies in the above example such as ViewModel and DelegateCommand, see my series of posts on MVVM.
EDIT after receiving more info: For tracking item selection, simply introduce a view model to represent the item:
public class CustomerViewModel : ViewModel
{
private bool isSelected;
public bool IsSelected
{
get { return this.isSelected; }
set
{
if (this.isSelected != value)
{
this.isSelected = value;
this.OnPropertyChanged(() => this.IsSelected);
}
}
}
}
Your "main" view model would expose a collection of these items (generally an ObservableCollection<T>):
public ICollection<CustomerViewModel> Customers
{
get { return this.customers; }
}
Your view would then bind as:
<ListBox ItemsSource="{Binding Customers}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Notice how each ListBoxItem will have its IsSelected property bound to the CustomerViewModel.IsSelected property. Thus, your main view model can just check this property to determine which customers are selected:
var selectedCustomers = this.Customers.Where(x => x.IsSelected);
The solution suggested by Kent is in my opinion by far the best/only one to follow MVVM.
If however you don't want to replicate/reflect listbox selections to the view model or you want a quick and - according to MVVM - dirty solution, you can use the command parameter to send data from the view to the view model.
For that you have to bind the CommandParameter property of the button to the property which contains the data you want to send to the view model. For simplicity I just used a TextBox.
<StackPanel Orientation="Vertical">
<TextBox x:Name="Data"/>
<Button Content="DoSomething"
Command="{Binding Path=DoSomethingCommand}"
CommandParameter="{Binding ElementName=Data, Path=Text}"/>
</StackPanel>
The ViewModel of the sample looks like the following.
public class ViewModel
{
private ICommand doSomethingCommand = new MyCommand();
public ICommand DoSomethingCommand
{
get
{
return doSomethingCommand;
}
}
}
With this, you will get the specified content as the parameter in the Execute method of ICommand.
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
string dataFromView = (string)parameter;
// ...
MessageBox.Show(dataFromView);
}
}

Categories