I have, till now, five different UserControls and the number can increase in future. I want to add them to a grid using MVVM pattern.
I have a class Apparatus that could serve as a viewModel for the UserControl
public class Apparatus
{
private string _appratusName;
private string _imageSource;
private string _experiments;
private GridProperties gridProps;
public Apparatus()
{
_appratusName = "";
_imageSource = "";
Experiments = "";
}
public Apparatus(string name, string imgSource, string exp)
{
_appratusName = name;
_imageSource = imgSource;
_experiments = exp;
}}
GridProperties is another class for containing the position of the UserControls in a grid.
public class GridProperties
{
public int Row;
public int Column;
public int ColumnSpan;
public int RowSpan;
public GridProperties(int row, int col, int rowSpan, int columnSpan)
{
this.Row = row;
this.Column = col;
this.RowSpan = rowSpan;
this.ColumnSpan = columnSpan;
}
there are public properties for each of the fields in the classes.
and also there's a ExperimentGridViewModel that's like
public class ExperimentGridViewModel : ViewModelBaseClass
{
private int _apparatusCount;
private string _workingExperiment;
private int _gridRowCount;
private int _gridColumnCount;
private ExperimentWindowViewModel _windowModel;
private ObservableCollection<Apparatus> _apparatusAppplied;}
I want to add UserControls, that are basically apparatuses like Burette, pippete etc. using MVVM.
I've looked though some answers for this and found out that this can be done using DataTemplates and ItemsControl but the problem is I have only one data type i.e Apparatus for all of my UserControls, Do I need to create different classes for each user control or Is there something else that I can do.
Now all I can do is some thing like this.
<Grid.Resources>
<DataTemplate DataType="{x:Type model:Apparatus }">
<apparatuses:Burette></apparatuses:Burette>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemsSource="{Binding AppliedApparatus}" >
</ItemsControl>
This adds burettes to grid, I can't add anything else as everything apparatus is of Type Appatatus.
Also, can any one please show me how could I bind the GridProperties in my viewModel to the the attached grid properties of the UserControls.
Related
I have a class with a property 'DesignParameters' that upon changing, would affect another property called 'AxialMomentDataLists'. However, the 'DesignParameters' is comprised of a bunch of other 'child' properties that are accessible through a datagrid on the UI and also implement property changed. If one of the child properties changes, I also want 'DesignParameters' to automatically update, which would in-turn call for a new 'AxialMomentDataLists' to be set. Does anyone have advice on the best method to achieve this?
public class Column : ObservableObject
{
private double length;
private DesignParameters desParameters;
public DesignParameters DesParameters
{
get { return desParameters; }
set
{
desParameters = value;
RaisePropertyChanged(nameof(DesParameters));
RaisePropertyChanged(nameof(AxialMomentDataLists));
}
}
public List<AxialMomentDataSet> AxialMomentDataLists
{
get { return CalculateAxialMomentLists(ColumnForce, DesParameters); }
set { }
}
}
Excerpt from child class:
public class DesignParameters : ObservableObject
{
#region Private variables
private double phiC;
private double phiS;
private int cover;
private int reinforcementYieldStrength;
private int concreteStrength;
private double b;
private double h;
private LiveLoadReductionType liveLoadReduction;
private StirrupType stirrupBar;
private int numberOfXBars;
private int numberOfYBars;
private BarDiameterType longitudinalBarDiameter;
private double longitudinalReinforcementPercentage;
List<Bar> rebar;
#endregion
public int NumberOfXBars
{
get { return numberOfXBars; }
set
{
numberOfXBars = PropertyMethods.SetNumberOfBars("x", value, B, H, Cover, LongitudinalBarDiameter);
RaisePropertyChanged(nameof(NumberOfXBars));
RaisePropertyChanged(nameof(Rebar));
RaisePropertyChanged(nameof(LongitudinalReinforcementPercentage));
RaisePropertyChanged(nameof(AxialResistance));
}
}
}
EDIT
I have created a more simple piece of code that tries to accomplish approximately the same thing that I am here (https://github.com/dtnaughton/SampleApp)
Basically, I had a UI that binds to properties on the FamilyMember and the properties Age and Name can be changed. On the Family (parent) class, I have a property CumulativeFamilyAge which returns a method that basically sums up the age of the family member (in this example, there is only 1 family member and therefore it should read the same as the FamilyMember.Age.
When the user changes FamilyMember.Age on the UI, I want Family to detect that one of it's child properties has changed, and therefore update CumulativeFamilyAge accordingly.
For example, if the 'NumberOfXBars' property gets changed (it is exposed on the UI) then I need the parent property 'DesignParameters' on the Column class to recognize one of it's child properties has changed and therefore this must update.
I'll write as I understand it.
In the Column class, you have a DesParameters property that contains an instance of DesignParameters.
If the value of one of the properties (NumberOfXBars) of this instance has changed, then you want to update all views associated with this instance.
Here you have an obvious problem with breaking the contract of the INotifyPropertyChanged interface. According to this contract, the PropertyChanged event does not fire when a value is assigned to a property, but only if this value is different from the previous one.
For this reason, if you just raise this event for the same value, then the bindings will not react to this.
As I think, you are simply not thinking correctly.
You don't need to update the bindings to the instance.
You need to update any bindings to any of its properties.
For this, the interface contract provides for the creation of an event with an empty argument.
If the above is a correct understanding of your problem, then try this implementation:
public int NumberOfXBars
{
get { return numberOfXBars; }
set
{
numberOfXBars = PropertyMethods.SetNumberOfBars("x", value, B, H, Cover, LongitudinalBarDiameter);
RaisePropertyChanged(string.Empty);
}
}
Example implementation after additional question:
I have created a more simple piece of code that tries to accomplish approximately the same thing that I am here (https://github.com/dtnaughton/SampleApp)
Improved implementation of Person entity using generic MVVMLight methods.
using GalaSoft.MvvmLight;
namespace SampleApp.Models
{
public class Person : ObservableObject
{
private string _name;
private int _age;
public string Name { get => _name; set => Set(ref _name, value); }
public int Age { get => _age; set => Set(ref _age, value); }
public Person()
{
Name = "TestName";
Age = 30;
}
}
}
Family is derived from ViewModelBase to be able to watch event properties change.
This is done to demonstrate how to observe changes in entities in properties.
Also added a second Person entity to improve the demo example.
using GalaSoft.MvvmLight;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SampleApp.Models
{
public class Family : ViewModelBase
{
private Person _familyMember;
private int _cumulativeFamilyAge;
private Person _childMember;
public Person FamilyMember { get => _familyMember; set => Set(ref _familyMember, value); }
public Person ChildMember { get => _childMember; set => Set(ref _childMember, value); }
public int CumulativeFamilyAge { get => _cumulativeFamilyAge; private set => Set(ref _cumulativeFamilyAge, value); }
public Family()
{
FamilyMember = new Person() { Name = "Father" };
ChildMember = new Person() { Name = "Son", Age = 7 };
}
public override void RaisePropertyChanged<T>([CallerMemberName] string propertyName = null, T oldValue = default, T newValue = default, bool broadcast = false)
{
base.RaisePropertyChanged(propertyName, oldValue, newValue, broadcast);
if (propertyName == nameof(FamilyMember) || propertyName == nameof(ChildMember))
{
if (oldValue is Person oldPerson)
oldPerson.PropertyChanged -= OnPersonPropertyChanged;
if (newValue is Person newPerson)
newPerson.PropertyChanged += OnPersonPropertyChanged;
CalculateCumulativeFamilyAge();
}
}
private void OnPersonPropertyChanged(object sender, PropertyChangedEventArgs e)
{
CalculateCumulativeFamilyAge();
}
public void CalculateCumulativeFamilyAge()
{
CumulativeFamilyAge = (FamilyMember?.Age ?? 0)
+ (ChildMember?.Age ?? 0);
return;
}
}
}
The Data Context of the Window is best defined in XAML - this makes XAML design much easier.
If you need to have a field in Code Behind with a link to the ViewModel, you should get it from XAML.
using SampleApp.ViewModels;
using System.Windows;
namespace SampleApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly MainViewModel viewModel /*= new MainViewModel()*/;
public MainWindow()
{
InitializeComponent();
//DataContext = viewModel;
viewModel = (MainViewModel)DataContext;
}
}
}
Added a DataTemplate for the Person entity in the Window and set the output for both entities.
<Window x:Class="SampleApp.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:viewmodels="clr-namespace:SampleApp.ViewModels"
xmlns:local="clr-namespace:SampleApp.Models"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<viewmodels:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:Person}">
<Border Margin="5" Background="LightSkyBlue" Padding="5">
<StackPanel>
<TextBox Text="{Binding Name}" Height="30"/>
<TextBox Text="{Binding Age}" Height="30"/>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<ContentPresenter Content="{Binding FamilyModel.FamilyMember}"/>
<ContentPresenter Content="{Binding FamilyModel.ChildMember}"/>
<TextBlock Text="{Binding FamilyModel.CumulativeFamilyAge}" Height="30"/>
</StackPanel>
</Grid>
</Window>
I am not sure if I unterstood you correctly. DesignParameters implements INotifyPropertyChanged and whenever one of its properties changes, you want to invoke PropertyChanged in your Column class for the AxialMomentDataLists property?
If so, this is quite easy. Just subscribe to this event whenever you set a new value to your DesParameters property. Don't forget to unsubscribe the event from the old value. A null check might be necessary (or do you use C# 8 with nullable reference types?)
public class Column : ObservableObject
{
private double length;
private DesignParameters desParameters;
public DesignParameters DesParameters
{
get { return desParameters; }
set
{
if(desParameters != null)
{
desParameters.PropertyChanged -= DesParametersChildPropertyChanged;
}
desParameters = value;
desParameters.PropertyChanged += DesParametersChildPropertyChanged;
RaisePropertyChanged(nameof(DesParameters));
RaisePropertyChanged(nameof(AxialMomentDataLists));
}
}
private void DesParametersChildPropertyChanged(object sender, PropertyChangeEventArgs args)
{
RaisePropertyChanged(nameof(DesParameters));
RaisePropertyChanged(nameof(AxialMomentDataLists));
}
public List<AxialMomentDataSet> AxialMomentDataLists
{
get { return CalculateAxialMomentLists(ColumnForce, DesParameters); }
set { }
}
}
I have a single ListBox that is supposed to display a picture with each item. I wrote codes and when I run it, pictures couldn't be displayed, but only text displayed. What have I done wrong in my codes? I made sure the image file path is correct.
I want to display each item with text (right side) and icon (left side).
WPF:
<ListBox Name="ListTest" DisplayMemberPath="Name" HorizontalAlignment="Left" Height="358" Margin="603,38,0,0" VerticalAlignment="Top" Width="361">
</ListBox>
C#
public partial class UserControl2 : UserControl
{
public UserControl2()
{
InitializeComponent();
this.LoadLogos();
}
private void LoadLogos()
{
this.ListTest.Items.Add(new CompanyDataContext("Adobe", "Adobe is a designing tool.", "/Company Logos/testDinner.jpg"));
this.ListTest.Items.Add(new CompanyDataContext("Facebook", "FedEx is a social networking website.", "/Company Logos/facebook.jpg"));
this.ListTest.Items.Add(new CompanyDataContext("FedEx", "FedEx is a courier company.", "/Company Logos/fedex.jpg"));
}
private class CompanyDataContext
{
public CompanyDataContext(string name, string about, string image)
{
this.Name = name;
this.About = about;
this.Image = image;
}
public string Name { get; private set; }
public string About { get; private set; }
public string Image { get; private set; }
}
}
You need a DataTemplate for CompanyDataContext as it does not inherit from Visual, WPF has no idea how to render it hence it calls the ToString Method on this.
This can be dealt with aDataTemplate for the ListBox
Untested template:
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="bord" CornerRadius="5" Margin="2" BorderBrush="LightGray" BorderThickness="3" Background="DarkGray">
<StackPanel Margin="5">
<TextBlock x:Name="txt" Text="{Binding Name}" FontWeight="Bold"/>
<Image Source="{Binding Image}" Height="100"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
Edited typo
I would like an additional WPF control that would add an int property to the TextBox class. I have tried Project > Add New Item > Custom Control (WPF). That gave me a new cs file for the new Control. I tried just having this new class inherit TextBox class and then adding public int number { get; set; } inside static CustomTextBox() but apparently that is not the correct syntax.
The TextBoxs I need this for are to be created dynamically in code, not in XAML.
Here is my attempt at implementing John Gardner's answer:
public static readonly DependencyProperty Number = DependencyProperty.RegisterAttached(
"number",
typeof(TextBox),
typeof(int),
new PropertyMetadata(false)
);
public static void SetNumber(UIElement element, TextBox value)
{
element.SetValue(Number, value);
}
public static TextBox GetNumber(UIElement element)
{
return (TextBox)element.GetValue(Number);
}
I added this in the MainWindow Class. It does not appear to give my TextBoxs the additional Number property.
You could just create a subclass of TextBox and add a single int property to it. That should do it I guess.
Take a look at this code to see an example of how to do it:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
panel.Children.Add(new MyTextBox { Number = 123 });
panel.Children.Add(new MyTextBox { Number = 321 });
panel.Children.Add(new MyTextBox { Number = 456 });
panel.Children.Add(new MyTextBox { Number = 654 });
}
private void click(object sender, RoutedEventArgs e)
{
var myTextBoxes = panel.Children.OfType<MyTextBox>();
var numbers = string.Empty;
myTextBoxes.ToList().ForEach(p => numbers += p.Number + Environment.NewLine);
MessageBox.Show(numbers);
}
}
//Subclass of TextBox that just adds one property
public class MyTextBox : TextBox
{
public int Number { get; set; }
}
..and the XAML just has the panel and a button:
<StackPanel Name="panel">
<Button Content="Show numbers" Click="click" />
</StackPanel>
Do you need a new control? You might be better off using an attached property instead. then no new control at all.
http://msdn.microsoft.com/en-us/library/cc265152(v=VS.95).aspx
Update:
an attached property doesn't add a property to the textbox directly, you'd access it like
YourClass.SetNumber( textbox, value );
int value = YourClass.GetNumber( textbox );
or in xaml,
<TextBox YourClass.Number="1"/>
your property should be "Number" in its string definition as well, you have "number". And your Get/Set calls should have an int value, not a textbox.
How do I get the value from the IntergerUpDown control?
I'am using this : http://wpftoolkit.codeplex.com/wikipage?title=NumericUpDown
Here is the code from MainWindow.xaml
<extToolkit:IntegerUpDown Value="{Binding Mode=TwoWay, Path=CurrentCount}" HorizontalAlignment="Left" Margin="119,111,0,148" Increment="1" Maximum="10" Minimum="1" Width="100"/>
Here is the code from MainWindow.xaml.cs
public int CurrentCount { get; set; }
private void button1_Click(object sender, RoutedEventArgs e)
{
CurrentCount = 10;
int len = Talker.BlahBlahBlah(textBox1.Text, CurrentCount);
}
So basically I want to pass the value chosen by the user as an int into the method BlahBlahBlah. Do I need to create a View Model and bind it to a CurrentValue property? Can you please provide me with sample code which gets the value from the UI?
So here is my Talker Class:
class Talker
{
public static int BlahBlahBlah(string thingToSay, int numberOfTimes)
{
string finalString = "";
for (int i = 0; i < numberOfTimes; i++)
{
finalString = finalString + thingToSay + "\n";
}
MessageBox.Show(finalString);
return finalString.Length;
}
}
This is the method inside ViewModelBase:
public virtual int CurrentCount
{
get { return _CurrentCount; }
set
{
_CurrentCount = value;
OnPropertyChanged("CurrentCount");
}
}
Question is how do I link it together??
Peace
Andrew
The proper, MVVM-correct way of doing this is to bind the Value to a property on your ViewModel.
public int CurrentCount
{
get { return _CurrentCount; }
set
{
_CurrentCount = value;
}
}
I recommend Josh Smith's excellent article on MVVM.
One thing you might want to do later is update the CurrentCount from code and have it reflect correctly in the IntegerUpDown control. To do this, you'll have to inherit from the INotifyPropertyChanged interface. You can use his ViewModelBase class which does just that. Then, your ViewModel inherits from ViewModelBase and can call OnPropertyChanged to send property changed notifications:
public int CurrentCount
{
get { return _CurrentCount; }
set
{
_CurrentCount = value;
base.OnPropertyChanged("CurrentCount");
}
}
One way to do it would be to name the IntegerUpDown control
<extToolkit:IntegerUpDown
x:Name="CurrentCountUpDown"
HorizontalAlignment="Left"
Margin="119,111,0,148"
Increment="1"
Maximum="10"
Minimum="1"
Width="100"/>
And then just refer to that control by name in your event handler:
private void button1_Click(object sender, RoutedEventArgs e)
{
int len = Talker.BlahBlahBlah(textBox1.Text, CurrentCountUpDown.Value);
}
I am trying to move the following function listView_SelectionChanged away from code-behind and handle it directly inside my ViewModel (or directly as XAML). And I was hoping that someone might have a better idea on how to implement this.
The TextBox contains Sections e.g. [Secion1] and to help navigate I have a ListBox on the side of the TextBox that contains a list of all Sections. If you click on one of the Sections it will automatically jump to that part of the Text.
The code currently looks something like this:
XAML
ListBox ItemsSource="{Binding Path=Sections}" Name="listBox"
SelectionMode="Single" Width="170"
DisplayMemberPath="Section"
SelectionChanged="listView_SelectionChanged"/>
<TextBox Name="TextBox1" Text="{Binding Path=Source}"/>
Model
public class SourceData
{
public SourceData()
{
Sections = new List<SectionData>();
}
public String Source { get; set; }
public List<SectionData> Sections { get; set; }
}
public class SectionData
{
public int Line { get; set; } // Line of the Section
public String Section { get; set; } // Section name (e.g. [Section1]
}
Code-behind
private void listView_SelectionChanged(object sender,
System.Windows.Controls.SelectionChangedEventArgs e)
{
var test = (SectionData)listBox.SelectedItem; // Get Information on the Section
if (test.Line > 0 && test.Line <= TextBox1.LineCount) // Validate
{
TextBox1.ScrollToLine(test.Line - 1); // Scroll to Line
}
}
In such situations I usually create an attached behavior (in your case it will be a behavior which will allow synchronizing textbox scrolled line), add property in ViewModel (SourceData) which will rule that attached behavior and bind behavior to this property.
Steps you should do in your case (I assume you know how to create an attached properties):
1) Create attached behavior ScrolledLine for textbox. It should support at least one-way binding. In attached property callback you will scroll textBox (to which behavior is attached) to the line. Below you will find a quick sample how to implement such a behavior.
2) Your SourceData should be extended with at least two properties: SelectedSection and ScrolledLine. ScrolledLine should be raising PropertyChanged. SelectedSection setter should change ScrolledLine:
private SectionData _selectedSection;
public SectionData SelectedSection
{
get { return _selectedSection; }
set
{
_selectedSection = value;
if (_selectedSection != null) SelectedLine = _selectedSection.Line;
}
}
3) Bind your view to these two new properties:
b below is xml-namespace for your attached behavior from #1
<ListBox ItemsSource="{Binding Path=Sections}" SelectionMode="Single" Width="170" DisplayMemberPath="Section" SelectedItem="{Binding SelectedSection, Mode=TwoWay}" />
<TextBox Text="{Binding Path=Source}" b:Behaviors.ScrolledLine="{Binding ScrolledLine}" />
4) Remove your listView_SelectionChanged event handler from view. Your view should not have any code except InitializeComponent from now on.
P.S.: Here is a sample how your attached behavior should look like:
public class b:Behaviors
{
#region Attached DP registration
public static int GetScrolledLine(TextBox obj)
{
return (int)obj.GetValue(ScrolledLineProperty);
}
public static void SetScrolledLine(TextBox obj, int value)
{
obj.SetValue(ScrolledLineProperty, value);
}
#endregion
public static readonly DependencyProperty ScrolledLineProperty=
DependencyProperty.RegisterAttached("ScrolledLine", typeof(int), typeof(Behaviors), new PropertyMetadata(ScrolledLine_Callback));
// This callback will be invoked when 'ScrolledLine' property will be changed. Here you should scroll a textbox
private static void ScrolledLine_Callback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var textbox = (TextBox) source;
int newLineValue = (int)e.NewValue;
if (newLineValue > 0 && newLineValue <= textBox.LineCount) // Validate
textbox.ScrollToLine(newLineValue - 1); // Scroll to Line
}
}