How can I update edited data in WPF using MVVM? [duplicate] - c#

This question already has answers here:
INotifyPropertyChanged WPF
(3 answers)
using of INotifyPropertyChanged
(3 answers)
Closed 4 months ago.
I am building a UI design for chat app and I encountered a problem when trying to update messages of a selected contact.
Uppon selecting a exsisting contact, choosing an edit option and then editing its properties like username and image, the only changes that are made are the username and the image of an contact. I still want to change the Username and image of a MessageModel which is included in my ContactModel as observable collection.
Here is a screenshot of not edited contact:
And a screenhsot of edited contact. You can se that there are only changes being applied on contacs and not in messages cards:
Here is my ContactModel class code:
namespace UV_MessagingApp.MVVM.Model
{
public class ContactModel
{
public string Username { get; set; }
public ImageSource ImageSource { get; set; }
public ObservableCollection<MessageModel> Messages { get; set; }
public string LastMessage => Messages.Last().Message;
}
}
Here is my MessageModel class code:
namespace UV_MessagingApp.MVVM.Model
{
public class MessageModel
{
public string Username { get; set; }
public string UsernameColor { get; set; }
public ImageSource ImageSource { get; set; }
public string Message { get; set; }
public DateTime Time { get; set; }
public bool? FirstMessage { get; set; }
}
}
Here is the binding from "EditContact.xaml". I bind the Username property from ContactModel and I want to achieve that the changes will also be applied to MessageModel properties.
<TextBox Grid.Column="1" Grid.Row="0" Name="contactUsername" Background="Transparent"
Foreground="White" BorderBrush="#292B2F" Margin="2" Padding="2"
Text="{Binding SelectedContact.Username, Mode=TwoWay}"/>
And finally this is the ListView from MainWindow.xaml which is a list where messages are displayed. You can see that I have a "chatItem" where the properties of a MessageModel are binded:
<ListView ItemsSource="{Binding SelectedContact.Messages}"
Background="Transparent"
BorderThickness="0"
ItemContainerStyle="{StaticResource ChatItem}"
Margin="8,0,0,0"
Grid.Row="1">
</ListView>
This is a ChatItem:
<StackPanel Orientation="Horizontal">
<Ellipse Width="30" Height="30"
Margin="10,0,0,-5">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding ImageSource}"
RenderOptions.BitmapScalingMode="Fant">
</ImageBrush>
</Ellipse.Fill>
</Ellipse>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Username}"
Foreground="{Binding UsernameColor}"
FontWeight="SemiBold"
VerticalAlignment="Center"
Margin="0,0,-5,0">
</Label>
<Label Content="{Binding Time}"
Foreground="#44474D"
FontWeight="SemiBold"
VerticalAlignment="Center">
</Label>
</StackPanel>
<Label Content="{Binding Message}"
Foreground="White"
FontWeight="SemiBold"
VerticalAlignment="Center">
</Label>
</StackPanel>
</StackPanel>
In the EditContact.caml window I tried passing in the SelectedContact.Messages.Username binding but it doesn't work. So I would appreciate your help.
EDIT:
Here is the MainVievModel class with already imlemented INotifyPropertyChanged() function.
public class MainViewModel : ObservableObject
{
//public ObservableCollection<MessageModel> Messages { get; set; }
public ObservableCollection<ContactModel> Contacts { get; set; }
/* Commands */
public RelayCommand SendCommand { get; set; }
private ContactModel _selectedContact;
public ContactModel SelectedContact
{
get{ return _selectedContact; }
set
{
_selectedContact = value;
OnPropertyChanged();
}
}
private string _message;
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
//Messages = new ObservableCollection<MessageModel>();
Contacts = new ObservableCollection<ContactModel>();
// Pošiljanje sporočil
//SendCommand = new RelayCommand(o =>
//{
// Messages.Add(new MessageModel
//
}
}

Related

Data Binding list to listbox C# XAML (Azure mobile services) UWP

So I have an online database. And I want the query results to be displayed in a listbox. I tried to use data binding but I think I'm doing it totally wrong.
Files
[![enter image description here][1]][1]
Table
class ProductTable
{
public string id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public string Type { get; set; }
public string Seller { get; set; }
public int Expiration { get; set; }
}
MainViewModel.cs
namespace Test
{
class MainViewModel
{
IMobileServiceTable<ProductTable> product = App.MobileService.GetTable<ProductTable>();
//In this method, load your data into Products
public async void Load()
{
// This query filters out completed TodoItems.
MobileServiceCollection<ProductTable, ProductTable> Products = await product
.Where(ProductTable => ProductTable.Price == 15)
.ToCollectionAsync();
// itemsControl is an IEnumerable that could be bound to a UI list control
IEnumerable itemsControl = Products;
}
}
}
XAML
<Page x:Name="PAGE"
xmlns:local="clr-namespace:Test;assembly=Version1">
<Grid>
<Grid.DataContext>
<local:MainViewModel />
</Grid.DataContext>
<ListBox Margin="10,10,10,100" x:Name="lb" ItemsSource="{Binding Products}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" FontSize="10"></TextBlock>
<TextBlock Text="{Binding Title}" FontSize="10"></TextBlock>
<TextBlock Text="{Binding Description}" FontSize="10"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Page>
Solution
Table needs JSON property name explicitly declared otherwise you won't be able to use data binding.
Table.cs
public class ProductTable
{
[JsonProperty(PropertyName = "id")]
public string id { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "Title")]
public string Title { get; set; }
[JsonProperty(PropertyName = "Description")]
public string Description { get; set; }
[JsonProperty(PropertyName = "Price")]
public double Price { get; set; }
[JsonProperty(PropertyName = "Type")]
public string Type { get; set; }
[JsonProperty(PropertyName = "Seller")]
public string Seller { get; set; }
[JsonProperty(PropertyName = "Expiration")]
public int Expiration { get; set; }
}
XAML.cs
You don't need to declare a new listbox, only use itemsource.
private async void button1_Click(object sender, RoutedEventArgs e)
{
IMobileServiceTable<ProductTable> productTest = App.MobileService.GetTable<ProductTable>();
// This query filters out completed TodoItems.
MobileServiceCollection<ProductTable, ProductTable> products = await productTest
.Where(ProductTable => ProductTable.Type == "Test")
.ToCollectionAsync();
lb.ItemsSource = products;
}
XAML
IN THE STACKPANEL, NEVER EVER USE HEIGHT"*" this will cause a critical error!
<ListBox x:Name="lb" Margin="10,10,10,100" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="300">
<TextBlock Text="{Binding Name}" FontSize="10"></TextBlock>
<TextBlock Text="{Binding Title}" FontSize="10"></TextBlock>
<TextBlock Text="{Binding Description}" FontSize="10"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You are creating two ListBoxes - one in code that isn't being displayed and one in XAML which is bound to whatever your DataContext is.
Remove the last two lines in your code, and change your XAML to:
ItemsSource="{Binding Products}"
Next, expose Products as a Property on a class (ViewModel if you want to use MVVM), and ensure that the DataContext of your ListBox is set to said ViewModel/class.
I'm not familiar with MobileServiceCollection, so I assume it is bindable. If it is not, expose Products as a ObservableCollection (if the values in it change over time), or any other supported collection type.
Elaboration on Properties
Create a class:
public class MainViewModel {
//This is a property
public ObservableCollection<ProductTable> Products { get; set; }
//In this method, load your data into Products
public void Load(){
//Products = xxx
}
}
In your XAML:
<Page [...]
xmlns:local="clr-namespace:YourNamespace;assembly=YourProjectName">
<Grid>
<Grid.DataContext>
<local:MainViewModel />
</Grid.DataContext>
<!-- ListBox goes in here somewhere, still with ItemsSource bound to Products -->
</Grid>
</Page>
Read more about namespaces here.
By doing this, your Windows' DataContext will be set to the MainViewModel (could also be named ProductsViewModel). Since your ListBox will be within your Window, it will inherit (due to Property Value Inheritance) the same DataContext.
The Binding will look for a property 'Products' on the ViewModel.
You will need to call the Load() method somewhere.
Part 2 - After looking at the code
Mind you, I am not able to run the code, so I'm flying somewhat blind.
Buy.xaml.cs
public sealed partial class Buy : Page
{
private readonly MainViewModel _viewModel;
public Buy()
{
this.InitializeComponent();
_viewModel = new MainViewModel();
DataContext = _viewModel;
}
private async void button1_Click(object sender, RoutedEventArgs e)
{
_viewModel.Load();
}
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
IMobileServiceTable<ProductTable> product = App.MobileService.GetTable<ProductTable>();
public List<ProductTable> Products { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
public async void Load()
{
Products = await product
.Where(ProductTable => ProductTable.Price == 15)
.ToListAsync();
//Notify that the property has changed to alert to UI to update.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Products)));
}
}
You need to make ProductTable public, or you will get a compilation error.
Hopefully, the above will enable you to bind your ListBox using the ItemsSource binding described above.
And please note that the above code is not necessarily best practice.
you don't need that line in your code behind
ListBox lb = new ListBox();
and you can keep that line
lb.ItemsSource = Products;
or you can sit the Binding to the MobileServiceCollection in the XAML
<ListBox Margin="10,10,10,100" x:Name="lb" ItemsSource="{Binding Products}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Description}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF ComboBox Set using Grid DataContext

I am new to C# WPF, I am tring to set DataContext to combobox my xml is a below
<Grid Name="groupEditArea" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFD8D8D8" Margin="-14,0,192,0">
<Label Content="Group Name" FontSize="18" HorizontalAlignment="Left" Margin="116,50,0,0" VerticalAlignment="Top" Width="136"/>
<Label Content="Group Type" FontSize="18" HorizontalAlignment="Left" Margin="116,123,0,0" VerticalAlignment="Top" Width="136"/>
<TextBox x:Name="groupGroupNameTxt" HorizontalAlignment="Left" FontSize="16" Height="31" Margin="368,50,0,0" TextWrapping="Wrap" Text="{Binding Path = GroupName, Mode=TwoWay, StringFormat=\{0:n3\}, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="226" TextChanged="groupGroupNameTxt_TextChanged" /> <!-- GotFocus="GroupGroupNameTxt_OnGotFocus" TextInput="GroupGroupNameTxt_OnTextInput" -->
<ComboBox x:Name="groupGroupNameCombo" HorizontalAlignment="Left" Margin="368,123,0,0" VerticalAlignment="Top" Width="226" Height="31" SelectionChanged="groupGroupNameCombo_SelectionChanged" DisplayMemberPath="GroupName" SelectedValuePath="CategoriesVal" SelectedValue="{Binding Categories}"/>
</Grid>
my POCO as below :-
public class Test: INotifyPropertyChanged
{
public Test()
{
}
public virtual string TestId { get; set; }
public virtual Categories CategoriesVal { get; set; }
public virtual string Name{ get; set; }
public virtual string GroupName
{
get { return Name; }
set
{
Name = value;
OnPropertyChanged("GroupName");
}
}
}
public class Categories : INotifyPropertyChanged
{
public Categories ()
{
}
public virtual string CategorieId { get; set; }
public virtual string Name{ get; set; }
public virtual string GroupName
{
get { return Name; }
set
{
Name = value;
OnPropertyChanged("GroupName");
}
}
}
}
and in my backend code i am setting DataContext as below :-
Categories cate = new Categories ();
cate.CategorieId = "cate1ID";
cate.GroupName = "CateGroupName1"
Test test = new Test();
test.TestId = "TestID";
test.CategoriesVal = cate;
test.Name = "TestName1";
and groupGroupNameCombo is set using ItemsSource and that contain entire list on Categories when i set using below its working fine
groupGroupNameCombo.SelectedItem = cate;
but when i set using below to grid it wont work :-
groupEditArea.DataContext = test;
can someone guide me how can i set combobox by setting grid DataContext instead of setting manually combobox.
instead
SelectedValuePath="CategoriesVal" SelectedValue="{Binding Categories}"
write
SelectedItem="{Binding CategoriesVal}"
SelectedValuePath means: the name of property (from the ComboBoxItem DataContex - Categories class In our case) from which the value of SelectedValue will be provided.
This is useful in case you want to representation of a instance-item (each form Combobox.Items) is not done by the item itself, but by one characteristic. In your case I do not see any point in it.
see more: Difference between SelectedItem, SelectedValue and SelectedValuePath

How to solve binding issues

I have a control that requires use of a checkbox to enable disable sections but i need to bind this to a class So I have done the following. So My question is how do I set the binding to the say for example IncidentBuilderProperty.IsEnabled should I do it code behind using a parameter.
<DockPanel DockPanel.Dock="Top" >
<Label Content="Display" />
<CheckBox Name="chkDisplayAdvanced" IsThreeState="False" VerticalAlignment="Center"
IsChecked="{Binding isEnabled, Mode=TwoWay}" />
</DockPanel>
But In My Model I have the property isEnabled Declared in the following way.
So my question is how would i access the property IncidentBuilderProperty.isEnabled bare in mind their is a screen for each property with this checkbox on it but referencing one custom control.
public class AssignedToMeViewData : WizardData, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ManagementPack ManagmentPack { get; set; }
public List<BuilderProperties> BuilderProperty { get; set; }
public BuilderProperties GeneralPage { get; set; }
public Exception LastKnownException { get; set; }
[DefaultValue(true)]
public bool IsNew { get; set; }
public BuilderProperties IncidentBuilderProperty { get; set; }
public BuilderProperties ProblemBuilderProperty { get; set; }
public BuilderProperties ServiceRequestBuilderProperty { get; set; }
public BuilderProperties ChangeRequestBuilderProperty { get; set; }
public BuilderProperties RleaseRequestBuilderProperty { get; set; }
public BuilderProperties ReviewActivityBuilderProperty { get; set; }
public BuilderProperties ManualActivityBuilderProperty { get; set; }
public class BuilderProperties
{
private bool isEnabled = true;
public bool IsEnabled {
get {
return isEnabled;
}
set {
if (isEnabled == value)
return;
isEnabled = value;
}
}
}
}
Having an AssignedToMeViewData in the control's DataContext, you can set up binding to all your BuilderProperties like this in xaml:
<DockPanel DockPanel.Dock="Top" >
<Label Content="Display" />
<CheckBox x:Name="chkIncident" IsChecked="{Binding IncidentBuilderProperty.IsEnabled, Mode=TwoWay}" VerticalAlignment="Center" />
<CheckBox x:Name="chkProblem" IsChecked="{Binding ProblemBuilderProperty.IsEnabled, Mode=TwoWay}" VerticalAlignment="Center" />
<CheckBox x:Name="chkService" IsChecked="{Binding ServiceRequestBuilderProperty.IsEnabled, Mode=TwoWay}" VerticalAlignment="Center" />
<CheckBox x:Name="chkChanges" IsChecked="{Binding ChangeRequestBuilderProperty.IsEnabled, Mode=TwoWay}" VerticalAlignment="Center" />
...
</DockPanel>
But note that Mode=TwoWay is the default for CheckBox.Checked, so you don't need to specify that.
If you have to do this from the code-behind instead, you can set up bindings like so:
chkIncident.SetBinding(CheckBox.IsCheckedProperty,
new Binding("IncidentBuilderProperty.IsEnabled"));
chkProblem.SetBinding(CheckBox.IsCheckedProperty,
new Binding("ProblemBuilderProperty.IsEnabled"));
chkService.SetBinding(CheckBox.IsCheckedProperty,
new Binding("ServiceRequestBuilderProperty.IsEnabled"));
...
See kishore's comment to your question for more advanced options when creating bindings from code-behind.
please try the following code snippet
FrameworkElement targetObject = chkDisplayAdvanced;
DependencyProperty targetProperty = CheckBox.IsCheckedProperty;
object sourceObject = IncidentBuilderProperty;//object of the actual Incidentbuilderclasss here
string sourceProperty = "IsEnabled";
var binding = new Binding(sourceProperty) {
Source = sourceObject, Mode = BindingMode.TwoWay
};
targetObject.SetBinding(targetProperty, binding);

Bind function to button in ListView using delegate in model

I'm new to C# and I'm trying to bind a function in each item of my collection to a button for that item. My collection is a List<AssessmentItem>, where each AssessmentItem is the following:
public class AssessmentItem
{
public string Label { get; set; }
public string Explanation { get; set; }
public string ResourceURL { get; set; }
public BitmapImage Icon { get; set; }
public RunFixer Fixer { get; set; }
}
public RunFixer Fixer is the delegate I want to bind to the button for that particular AssessmentItem. Here is the DataTemplate I'm using to accomplish my plans:
<DataTemplate x:Key="AssessmentListTemplate">
<Grid Margin="0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Label}" FontSize="16" VerticalAlignment="Center" Grid.Column="0" FontWeight="Bold" Margin="15,0,0,0"/>
<Button Grid.Column="2" Margin="0,0,10,0">
<Image Source="{Binding Icon}" Width="64" Height="64"/>
</Button>
</Grid>
</DataTemplate>
How do I bind the <Button>'s Click handler to my RunFixer delegate? I tried {Binding Fixer}, which didn't work. I also changed Fixer to a MouseButtonEventHandler, but that didn't work either.
Thank you for your time looking at this! I don't mind being educated.
Additions
The RunFixer delegate is declared with
public delegate void RunFixer();
Final Code
For my personal documentation and for other's satisfaction, I'm posting the result that worked well for me:
public class AssessmentItem
{
public string Label { get; set; }
public string Explanation { get; set; }
public string ResourceURL { get; set; }
public BitmapImage Icon { get; set; }
public RunFixer Fixer { get; set; }
DelegateCommand _fixerCommand = null;
public ICommand FixerCommand
{
get
{
if (_fixerCommand == null)
{
_fixerCommand = new DelegateCommand(() => Fixer());
}
return _fixerCommand;
}
}
}
And in the DataTemplate:
<DataTemplate x:Key="AssessmentListTemplate">
<Grid Margin="0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Label}" FontSize="16" VerticalAlignment="Center" Grid.Column="0" FontWeight="Bold" Margin="15,0,0,0"/>
<Button Grid.Column="2" Margin="0,0,10,0" Command="{Binding FixerCommand}">
<Image Source="{Binding Icon}" Width="64" Height="64"/>
</Button>
</Grid>
</DataTemplate>
Hope this helps!
You can't bind a function to a button. But you can bind command object to a button's Command property. In order to do this you need to modify your class:
public class AssessmentItem
{
public string Label { get; set; }
public string Explanation { get; set; }
public string ResourceURL { get; set; }
public BitmapImage Icon { get; set; }
public RunFixer Fixer { get; set; }
ICommand _fixerCommand = new UICommand();
public ICommand FixerCommand {
get
{
_fixerCommand = _fixerCommand ?? new DelegateCommand<object>((o)=>
{var f = Fixer;
if(f != null) f();});
return _fixerCommand;}
}
}
I'm using DelegateCommand class which is a part of Prism library which can be downloaded here
Then you're modifying your data template to bind to this FixerCommand property
<Button Grid.Column="2" Margin="0,0,10,0" Command ="{Binding FixerCommand}">
<Image Source="{Binding Icon}" Width="64" Height="64"/>
</Button>
You can't bind the "Click" event of a button because it is not a dependency property (in this case it is a routed event). Luckily, WPF gives us the "Command" property (which is a dependency property) for a button!
Your binding will look like:
Command="{Binding RunFixerCommand}"
Your data object will expose an ICommand property which will return a command object that calls "RunFixer". A great example of a reusable and easy to use generic command class can be found in this blog post.
Sample (in your AssessmentItem class):
public ICommand RunFixerCommand {get; private set;}
public AssessmentItem()
{
RunFixerCommand = new DelegateCommand((p) => RunFixer());
}

Get Object properties of selected list item

I have a listbox that has a listsource that is a list of 'results' objects. The results class looks like this:
public class Result
{
public string GUID { get; set; }
public string __invalid_name__I { get; set; }
public string FN { get; set; }
public string DOB { get; set; }
public string SEX { get; set; }
public string SN { get; set; }
public string __invalid_name__U { get; set; }
public string TYPE { get; set; }
//Gender icon path associated with result
public string SexIcon { get; set; }
}
And this is what my listbox looks like in the xaml code:
<ListBox
Height="517"
HorizontalAlignment="Left"
Margin="12,84,0,0"
Name="searchList"
VerticalAlignment="Top"
Width="438"
SelectionChanged="SearchList_SelectedEvent">
<!-- What each listbox item will look like -->
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Sex, Converter={StaticResource SexToSourceConverter}}" Visibility="Visible" />
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Name="FirstName" Text="{Binding FN}" FontSize="28" Margin="0,0,10,0"/>
<TextBlock Name="LastName" Text="{Binding SN}" FontSize="28" />
</StackPanel>
<TextBlock Text="{Binding DOB}" FontSize="24" />
<!-- Line Stroke="#FF04863C" StrokeThickness="3" X1="100" Y1="100" X2="300" Y2="100" / -->
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So my question is, (and what I'm really struggling with) is how does one for instance get the value of the GUID property of the selected Item (which is basically a results object)?
(searchList.SelectedItem as Result).GUID

Categories