What i m doing wrong?
Xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="26*"/>
<RowDefinition Height="63*"/>
<RowDefinition Height="67*"/>
</Grid.RowDefinitions>
<TextBlock Name ="title" Text="" TextWrapping="NoWrap" Grid.Row="0" Margin="25,10,25,0"/>
<Image Source="{Binding Path=BindImgURL}" HorizontalAlignment="Left" Height="164" Grid.Row="1" VerticalAlignment="Top" Width="306" Margin="152,0,0,0"/>
</Grid>
C# Code:
private string Id;
private string ImgURL;
public VideoDetails(string Id)
{
InitializeComponent();
this.Id = Id;
DetailsGenerator dg = new DetailsGenerator(Id);
this.ImgURL = "Dynamic source";
this.DataContext = BindImgURL;
MessageBox.Show(BindImgURL);
}
public string BindImgURL
{
get { return ImgURL; }
set
{ ImgURL = value; }
}
Displaying Source correct!
checked by MessageBox.But No image
what i did wrong? I have tried removing "path=" but doesn't worked
I imagine you either want to set DataContext to this
this.DataContext = this;
or change binding to current DataContext
<Image Source="{Binding}" .../>
at the moment Image will search for BindImgURL in current DataContext, which is set to BindImgURL and that is a string
Not related to your problem but I would also suggest you look into implementing INotifyPropertyChanged and raising PropertyChanged event for BindImgURL as, at the moment, after you set DataContext any change to BindImgURL won't be picked up by UI
Related
I have been trying to create a small application to sit over a POS interface and allow the user to access some information not accessible through the standard POS software by external custom C# applications. One of these was a receipt lookup, which after a demonstration was asked to be expanded to also check online order details.
The problem I am having is with the databinding to the object which is storing the information. Originally when there was a single view and viewmodel for the function it worked correctly. After creating 2 usercontrol views to present different information, with corresponding viewmodels for each view, the new views do not show any data.
Here is the base class inherited by the Model:
using System;
using System.ComponentModel;
using System.Diagnostics;
namespace RGLibrary
{
public abstract class Observable_Object : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public virtual void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
}
}
The inherited class by the VMs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace RGLibrary
{
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
{
if (Equals(member, val))
return false;
member = val;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the core VM class:
using RGLibrary;
using Store_Launcher.Model;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Store_Launcher.ViewModel
{
public class ReceiptLookupVM: BindableBase
{
private string _ReceiptNumber;
private ReceiptLookupModel _ReceiptDetails;
public ReceiptLookupModel ReceiptDetails
{
get { return _ReceiptDetails; }
set { _ReceiptDetails = value; OnPropertyChanged("ReceiptDetails"); }
}
public RGDBConnect rms = new RGDBConnect("");
public string ReceiptNumber
{
get { return _ReceiptNumber; }
set { _ReceiptNumber = value; OnPropertyChanged("ReceiptNumber"); }
}
private OnlineOrderDetailsVM orderDetailsVM = new OnlineOrderDetailsVM();
private ReceiptDetailsVM receiptDetailsVM = new ReceiptDetailsVM();
private BindableBase _CurrentMode;
public BindableBase CurrentMode
{
get { return _CurrentMode; }
set { SetProperty(ref _CurrentMode, value); }
}
public ReceiptLookupVM()
{
ReceiptDetails = new ReceiptLookupModel();
ReceiptNumber = "";
if (System.Diagnostics.Debugger.IsAttached)
rms = new RGDBConnect(ConfigurationManager.AppSettings["rmstest"]);
else
rms = new RGDBConnect(ConfigurationManager.AppSettings["rms"]);
CheckCommand = new MyICommand<string>(OnCheck);
CurrentMode = receiptDetailsVM;
}
public MyICommand<string> CheckCommand { get; private set; }
private void OnCheck(string command)
{
ReceiptDetails.Receipt = _ReceiptNumber;
string query = "rg_launcher_receiptref_ext '" + ReceiptDetails.Receipt + "'";
try
{
DataTable results = rms.ExecuteSelect(query);
if (results.Rows.Count == 1)
{
DataRow resultRow = results.Rows[0];
if (resultRow["tran_type"].ToString() == "SALE")
{
ReceiptDetails.SaleCode = resultRow["sale_code"].ToString();
ReceiptDetails.Customer = resultRow["name"].ToString();
ReceiptDetails.CustomerID = resultRow["customer_id"].ToString();
ReceiptDetails.Items = resultRow["units"].ToString();
ReceiptDetails.Value = resultRow["value"].ToString();
ReceiptDetails.Stylist = resultRow["stylist"].ToString();
ReceiptDetails.TransactionType = ReceiptLookupModel.TransType.RetailOrder;
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
else if (resultRow["tran_type"].ToString() == "WEB ORDER")
{
ReceiptDetails.SaleCode = resultRow["sale_code"].ToString();
ReceiptDetails.ReceiptNumber = resultRow["receipt_ref"].ToString();
ReceiptDetails.Customer = resultRow["name"].ToString();
ReceiptDetails.CustomerID = resultRow["customer_id"].ToString();
ReceiptDetails.Items = resultRow["units"].ToString();
ReceiptDetails.Value = resultRow["value"].ToString();
ReceiptDetails.TransactionType = ReceiptLookupModel.TransType.OnlineOrder;
orderDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = orderDetailsVM;
}
else
{
MessageBox.Show(
"Unable to determine the transaction type for this number. Please contact IT for assistance",
"Receipt Lookup: Unknown order number",
MessageBoxButton.OK,
MessageBoxImage.Warning);
ReceiptDetails = new ReceiptLookupModel();
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
}
else if (results.Rows.Count == 0)
{
MessageBox.Show(
"Unable to find this receipt number in the system. Please make sure that the receipt number has been entered correctly.",
"Receipt Lookup: Unable to find sale",
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
ReceiptDetails = new ReceiptLookupModel();
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
else
{
MessageBox.Show(
"An error has occured and the system is unable to properly locate this receipt number in the system. Please contact IT for assistance",
"Receipt Lookup: Unable to find sale",
MessageBoxButton.OK,
MessageBoxImage.Warning);
ReceiptDetails = new ReceiptLookupModel();
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
}
catch (Exception e)
{
MessageBox.Show(
e.Message,
"Receipt Lookup: An error has occurred",
MessageBoxButton.OK,
MessageBoxImage.Warning);
MessageBox.Show(
"An error has occured and the system is unable to properly locate this receipt number in the system. Please check to make sure your computer is currently connected to the internet. Contact IT for further assistance",
"Receipt Lookup: Unable to lookup receipt number",
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
ReceiptDetails = new ReceiptLookupModel();
receiptDetailsVM.UpdateDetails(ReceiptDetails);
CurrentMode = receiptDetailsVM;
}
}
}
}
Here is the corresponding view:
<Window x:Class="Store_Launcher.Views.ReceiptLookupView"
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:local="clr-namespace:Store_Launcher.Views"
xmlns:viewmodel="clr-namespace:Store_Launcher.ViewModel"
mc:Ignorable="d"
Title="Rodd & Gunn Launcher: Receipt Lookup" Height="195" Width="450"
ShowInTaskbar="True" ResizeMode="NoResize" Topmost="True" >
<Window.DataContext>
<viewmodel:ReceiptLookupVM/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type viewmodel:OnlineOrderDetailsVM}">
<local:OnlineOrderDetailsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodel:ReceiptDetailsVM}">
<local:ReceiptDetailsView/>
</DataTemplate>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
Executed="CloseCommandHandler"/>
</Window.CommandBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="AUTO"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Grid.Column="0"
Orientation="Horizontal"
Margin="5"
VerticalAlignment="Center">
<TextBlock Text="Receipt Number: "/>
<TextBox Width="100"
Text="{Binding ReceiptNumber, Mode=TwoWay}"/>
</StackPanel>
<!-- New User Control XAML to switch between brick and mortar, and online order modes -->
<UserControl
Margin="5"
Height="115"
Width="230"
Grid.Column="1">
<ContentControl Content="{Binding CurrentMode}"/>
</UserControl>
<!-- Original Grid XAML -->
<!--<Grid Grid.Row="0"
Grid.Column="1"
Margin="5"
DataContext="{Binding ReceiptDetails}">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="AUTO"/>
<ColumnDefinition Width="AUTO"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Text="Sale Code: "/>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Text="{Binding SaleCode}"/>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Text="Customer ID: "/>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Text="{Binding CustomerID}"/>
<TextBlock
Grid.Row="2"
Grid.Column="0"
Text="Customer: "/>
<TextBlock
Grid.Row="2"
Grid.Column="1"
Text="{Binding Customer}"/>
<TextBlock
Grid.Row="3"
Grid.Column="0"
Text="Items: "/>
<TextBlock
Grid.Row="3"
Grid.Column="1"
Text="{Binding Items}"/>
<TextBlock
Grid.Row="4"
Grid.Column="0"
Text="Value: "/>
<TextBlock
Grid.Row="4"
Grid.Column="1"
Text="{Binding Value}"/>
<TextBlock
Grid.Row="5"
Grid.Column="0"
Text="Stylist: "/>
<TextBlock
Grid.Row="5"
Grid.Column="1"
Text="{Binding Stylist}"/>
</Grid>-->
<StackPanel
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Center">
<Button
Width="100"
Height="20"
Margin="5"
Content="CHECK"
Command="{Binding CheckCommand}"/>
<Button
Width="100"
Height="20"
Margin="5"
Content="CLOSE"
Command="ApplicationCommands.Close"/>
</StackPanel>
</Grid>
</Window>
Here is the order details model:
using RGLibrary;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Store_Launcher.Model
{
public class ReceiptLookupModel: Observable_Object
{
private string _Receipt;
private string _SaleCode;
private string _ReceiptNumber;
private string _CustomerID;
private string _Customer;
private string _Items;
private string _Value;
private string _Stylist;
private TransType? _TransactionType;
public string Receipt
{
get { return _Receipt = (_Receipt ?? ""); }
set { _Receipt = value; OnPropertyChanged("Receipt"); }
}
public string SaleCode
{
get { return _SaleCode = (_SaleCode ?? ""); }
set { _SaleCode = value; OnPropertyChanged("SaleCode"); }
}
public string ReceiptNumber
{
get { return _ReceiptNumber = (_ReceiptNumber ?? ""); }
set { _ReceiptNumber = value; OnPropertyChanged("ReceiptNumber"); }
}
public string CustomerID
{
get { return _CustomerID = (_CustomerID ?? ""); }
set { _CustomerID = value; OnPropertyChanged("CustomerID"); }
}
public string Customer
{
get { return _Customer = (_Customer ?? ""); }
set { _Customer = value; OnPropertyChanged("Customer"); }
}
public string Items
{
get { return _Items = (_Items ?? "0"); }
set { _Items = value; OnPropertyChanged("Items"); }
}
public string Value
{
get { return _Value = (_Value ?? "$0.00"); }
set { _Value = value; OnPropertyChanged("Value"); }
}
public string Stylist
{
get { return _Stylist = (_Stylist ?? ""); }
set { _Stylist = value; OnPropertyChanged("Stylist"); }
}
public TransType? TransactionType
{
get { return _TransactionType = (_TransactionType ?? TransType.None); }
set { _TransactionType = value; OnPropertyChanged("TransactionType"); }
}
public enum TransType
{
OnlineOrder,
RetailOrder,
None
}
}
}
The online orders viewmodel:
using RGLibrary;
using Store_Launcher.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Store_Launcher.ViewModel
{
public class OnlineOrderDetailsVM: BindableBase
{
private ReceiptLookupModel _OrderDetails;
public ReceiptLookupModel OrderDetails
{
get { return _OrderDetails; }
set { _OrderDetails = value; OnPropertyChanged("OrderDetails"); }
}
public OnlineOrderDetailsVM()
{
OrderDetails = new ReceiptLookupModel();
}
public void UpdateDetails(ReceiptLookupModel SQLData)
{
ReceiptLookupModel _data = new ReceiptLookupModel();
_data.Customer = SQLData.Customer;
_data.CustomerID = SQLData.CustomerID;
_data.Items = SQLData.Items;
_data.Receipt = SQLData.Receipt;
_data.ReceiptNumber = SQLData.Receipt;
_data.SaleCode = SQLData.SaleCode;
_data.Stylist = SQLData.Stylist;
_data.TransactionType = SQLData.TransactionType;
_data.Value = SQLData.Value;
OrderDetails = _data;
}
}
}
Here is the order details view:
<UserControl x:Name="OnlineOrderDetailsUC"
x:Class="Store_Launcher.Views.OnlineOrderDetailsView"
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"
xmlns:local="clr-namespace:Store_Launcher.Views"
xmlns:viewmodel="clr-namespace:Store_Launcher.ViewModel"
mc:Ignorable="d"
d:DesignHeight="115" d:DesignWidth="230">
<UserControl.DataContext>
<viewmodel:OnlineOrderDetailsVM/>
</UserControl.DataContext>
<Grid Grid.Row="0"
Grid.Column="1"
Margin="5"
DataContext="{Binding OrderDetails, NotifyOnSourceUpdated=True}">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="AUTO"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Text="Sale Code: "/>
<TextBlock
Grid.Row="0"
Grid.Column="1"
Text="{Binding SaleCode}"/>
<TextBlock
Grid.Row="1"
Grid.Column="0"
Text="Receipt No: "/>
<TextBlock
Grid.Row="1"
Grid.Column="1"
Text="{Binding ReceiptNumber}"/>
<TextBlock
Grid.Row="2"
Grid.Column="0"
Text="Customer ID: "/>
<TextBlock
Grid.Row="2"
Grid.Column="1"
Text="{Binding CustomerID}"/>
<TextBlock
Grid.Row="3"
Grid.Column="0"
Text="Customer: "/>
<TextBlock
Grid.Row="3"
Grid.Column="1"
Text="{Binding Customer}"/>
<TextBlock
Grid.Row="4"
Grid.Column="0"
Text="Items: "/>
<TextBlock
Grid.Row="4"
Grid.Column="1"
Text="{Binding Items}"/>
<TextBlock
Grid.Row="5"
Grid.Column="0"
Text="Value: "/>
<TextBlock
Grid.Row="5"
Grid.Column="1"
Text="{Binding Value}"/>
</Grid>
</UserControl>
The thing I am confused about is that in the receiptLookup view, when I use the commented out grid instead of usercontrol section that binds to the model object in the receiptLookup viewmodel correctly but the usercontrol binding does not seem to work.
I have tried a number of things to resolve this. Originally instead of using a method inside the orderdetails viewmodel to set the model's object value I was just setting it to be the same as the one generated in the receiptlookup viewmodel.
In addition I have tried to use diag:PresentationTraceSources.TraceLevel=High in the binding to diagnose if there is a binding error based on other questions that have been asked previously. I think that it is binding to something because there is no error about the binding failing, and it lists a hash for an object that is binding to.
Further to this I have tried to remove the datacontext from both the orderdetails view's usercontrol and also from the datagrid's datacontext, and specify it in the binding path but that did not change any results either.
Another question was given the advice to add an x:name to their views, so I have done the same without any changes. I have also tried adding NotifyOnSourceUpdate=True to each textblock's binding and Mode=TwoWay but neither on their own or the combination of both has helped.
I have used a similar usercontrol sub-section of the main view in a few previous applications at this point so I am really quite stuck as to why it is not working in this case. I have tried to add all the relevant code but if you need me to list anything else please let me know in your comment.
If you have a UserControl in a DataTemplate like
<DataTemplate DataType="{x:Type viewmodel:OnlineOrderDetailsVM}">
<local:OnlineOrderDetailsView/>
</DataTemplate>
the UserControl is supposed to inherit its DataContext from its parent object, which is a ContentControl or a ContentPresenter here. The DataContext holds an instance of the type specified by the DataTemplate's DataType property (i.e. the Content of the ContentControl or ContentPresenter).
This property value inheritance only works if you do not explicity set the property, as in
<UserControl.DataContext>
<viewmodel:OnlineOrderDetailsVM/>
</UserControl.DataContext>
This DataContext value has higher precedence than the inherited value. The result is that the UserControl in the DataTemplate always binds to its private DataContext object, but never to the one provided by the template.
So just remove the DataContext assignment from the UserControl's XAML. Controls should never explicitly set their own DataContext, hence never have private view model objects.
I am (new in C# and WPF and) trying to bind data from more sources (class properties) and I am a bit confused from different guides and advices. I want to dynamically add Users in userList and showing for example the last insert and whole list at the same time. That is done on different place in source code, but simple like in contructor of my example. How and where should I set binding and datacontext for those three elements (myName,myTitle and myUserList) to reflect changes in main class properties? Should I call every time function for update binding target, or set this.datacontext after editing properties? Or should I bind to some function (if it's possible) which returns the value I need? I am a bit lost with binding to property of object and datacontexts etc. Here is an example from what I have:
<Window x:Name="Window" x:Class="WpfTest.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:local="clr-namespace:WpfTest">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBox x:Name="myName" Text="" Grid.Column="1"/>
<TextBox x:Name="myTitle" Text="" Grid.Column="0"/>
</StackPanel>
<ListBox x:Name="myUserList">
</ListBox>
</Grid>
</Window>
And
public partial class MainWindow : Window {
public User myUser;
public Dictionary<int,User> userList= new Dictionary<int, User>();
public object SubWindow { get; private set; }
public MainWindow() {
newUser = new User();
newUser.Title = "John Doe";
newUser.Name = "Dr.";
this.userList.Add(index,newUser);
this.myUser=newUser;
InitializeComponent();
}
}
And
public class User
{
public String Name { get; set; }
public String Title { get; set; }
}
Thanks for any advice.
First thing is first, WPF works best when you are working with MVVM, the general idea is implementing the INotifyPropertyChanged, what ever items you add to you change, will propagate to the framework and update your views.
When working with Lists, use an ObservableCollection. If you want to add items dynamically to it, you would need to modify the ObservableCollection instead. For best results, in your UserControl, use a DataTemplate for the specific type to display a formatted version of your values.
For the second part, showing the last added item, there are a few ways you can go about this, the best would be to add a new Items(Grid, Stackpanel, etc) that can hold data, use Binding to set its value to a the same context as your list(i.e the ObservableCollection) and create a Converter that will use the ObservableCollection as input, inside your specific converter implementation, just get the last item added and Display it to the control you want( you can use a dataTemplate for this as well)
Solution: You have to bind input data in model class(User) and use model to insert data in the listbox like below
<Window x:Class="WpfRegistration.Listbox"
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:local="clr-namespace:WpfRegistration"
mc:Ignorable="d"
Title="Listbox" Height="450" Width="800">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.15*"></RowDefinition>
<RowDefinition Height="0.85*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="63*"></ColumnDefinition>
<ColumnDefinition Width="26*"></ColumnDefinition>
<ColumnDefinition Width="109*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBox x:Name="myName" Text="" Grid.Column="1"/>
<TextBox x:Name="myTitle" Text="" Grid.Column="0"/>
</StackPanel>
<StackPanel>
<Button Height="23" Margin="50,40,0,0" Name="button1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="76" Click="Button1_OnClick" >Add Item</Button>
</StackPanel>
<StackPanel>
<Button Height="23" Margin="140,40,10,12" Name="DeleteButton" VerticalAlignment="Top" Click="DeleteButton_Click">Delete Item</Button>
</StackPanel>
<ListBox Grid.Row="1" Grid.Column="0" BorderThickness="3" BorderBrush="Black" Margin="0,60,0,100" x:Name="myUserList">
</ListBox>
</Grid>
</Grid>
Xaml.cs
public partial class Listbox : Window
{
public Listbox()
{
InitializeComponent();
User newUser = new User();
newUser.Title = "John Doe";
newUser.Name = "Dr.";
this.myUserList.Items.Add(newUser.Title + newUser.Name);
}
private void Button1_OnClick(object sender, RoutedEventArgs e)
{
User newUser = new User();
newUser.Title = myTitle.Text;
newUser.Name = myName.Text;
myUserList.Items.Add(newUser.Name + " " + newUser.Title );
myTitle.Text=String.Empty;
myName.Text=String.Empty;
}
private void DeleteButton_Click(object sender, RoutedEventArgs e)
{
myUserList.Items.RemoveAt(myUserList.Items.IndexOf(myUserList.SelectedItem));
}
}
public class User
{
public string name;
public string title;
public String Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("name");
}
}
public string Title
{
get { return title; }
set
{
title = value;
OnPropertyChanged("title");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This question already has answers here:
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
(21 answers)
Closed 6 years ago.
I would greatly appreciate some help with this binding issue I'm having. Basically I have a list view showing some information about Files. In the list view item itself, there's some text and also a button.
When this button is clicked I want to disable that button.
Currently I've set up an ObservableCollection - however even though the button click is being registered, the UI doesn't update. If I go to a different screen and return, then the UI updates. So it's not instantaneous.
I think there is some problem with the way RaisePropertyChanged() is working. I know from reading other SO articles that property changes in the object are harder to pick up than say, removing an item or adding an item to the ListView.
I'm completely stuck, any help would be most appreciated. Thanks.
Xaml:
<ListView RelativePanel.Below="heading" ItemsSource="{Binding Pages}" ReorderMode="Enabled" CanReorderItems="True" AllowDrop="True" Margin="0,10" SelectedItem="{Binding Path=SelectedFile,Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:File">
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path= Name, Mode=TwoWay}" FontWeight="Bold" Padding="0,5" />
<TextBlock Text ="{x:Bind Path}" Grid.Row="1" TextWrapping="Wrap" Padding="10,0,0,0" Foreground="DarkGray" Opacity="0.8" />
<Button Content="X" Grid.Column="1" Grid.RowSpan="2" Command="{x:Bind EnableCommand}" IsEnabled="{x:Bind Path=IsEnabled, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
File.cs:
public class File : ViewModelBase
{
public string Name { get; set; }
public string FileName { get; set; }
public string Path { get; set; }
public string Contents { get; set; }
private Boolean isEnabled = true;
public Boolean IsEnabled {
get { return isEnabled; }
private set {
isEnabled = value;
RaisePropertyChanged("IsChecked");
}
}
private ICommand enableCommand;
public ICommand EnableCommand
{
get
{
if(enableCommand == null)
{
enableCommand = new RelayCommand(() => {
isEnabled = false;
Name += "Disabled";
RaisePropertyChanged();
});
}
return enableCommand;
}
}
}
Viewmodel:
public class MyPageViewModel : BaseViewModel
{
private ObservableCollection<File> pages;
public ObservableCollection<File> Pages
{
get { return pages; }
set
{
pages = value;
RaisePropertyChanged();
}
}
private File selectedFile = new File();
public File SelectedFile
{
get { return selectedFile; }
set
{
Set(ref selectedFile, value);
}
}
public MyPageViewModel()
{
if (ApplicationData.FileList != null)
{
Pages = new ObservableCollection<File>(ApplicationData.FileList);
}
else
{
Pages = new ObservableCollection<File>();
}
}
You notify IsChecked when you should be notifying IsEnabled.
(ObsevarvableCollection only notifies when something is added or removed from it. Changes in the objects it holds are not notified by it.)
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Menupedia.MiniRestaurantViewer"
x:Name="UserControl" Width="80" Height="100">
<Grid x:Name="LayoutRoot">
<Label x:Name="label_Name" Content="{Binding _name, Mode=OneWay}" HorizontalAlignment="Stretch" Width="80" FontFamily="Public Enemy NF" FontSize="14.667" Foreground="#FFEF7B54" Margin="0" Height="20" VerticalAlignment="Bottom"/>
<Image x:Name="image_Logo" Source="{Binding _logo, Mode=OneWay}" HorizontalAlignment="Left" Width="80" Height="80" VerticalAlignment="Top"/>
<Border BorderBrush="#FFF15A28" BorderThickness="1" Height="80" CornerRadius="2" VerticalAlignment="Top" Width="80" HorizontalAlignment="Left"/>
</Grid>
public partial class MiniRestaurantViewer : UserControl
{
public int _id {get{return id;}}
public string _name {get{return name;}}
public ImageSource _logo {get{return logo;}}
public MiniRestaurantViewer(int id, string name,byte[] logo)
{
this.id = id;
this.name = name;
this.logo = ByteArrayToImageSource(logo);
this.InitializeComponent();
}
private int id;
private string name;
private ImageSource logo;
private ImageSource ByteArrayToImageSource(byte[] data)
{
BitmapImage image = null;
if (null != data)
{
image = new BitmapImage();
image.BeginInit();
image.StreamSource = new System.IO.MemoryStream(data);
image.EndInit();
}
return image;
}
public MiniRestaurantViewer()
{
this.InitializeComponent();
}
}
that is my custom control. i want to do this
ListBox.Items.Add(new MiniRestaurantViewer(1,"test",null));
when i do it i see the UI element but it's empty (binding didn't work). Through the watch though i find that the public properties has values.. i don't know how to make it work and i have been tryin since 3 days please help me. :(
You need to set DataContext to itself if properties lies in code behind.
<UserControl x:Class="Menupedia.MiniRestaurantViewer"
x:Name="UserControl" Width="80" Height="100"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
You can do this this.DataContext = this; to make your code works but you are far far away from the best practice of wpf which means using MVVM try to read this first as it can be a good start for a begineer
public MiniRestaurantViewer(int id, string name,byte[] logo)
{
this.id = id;
this.name = name;
this.logo = ByteArrayToImageSource(logo);
this.InitializeComponent();
this.DataContext = this;
}
First of all, I think you need to use two way bindings to be able to make changes both way - from your view to model and from model to view. I did not see in your control Listbox, so probably it's from your mainWindow. In this case your have two oportunities - or to set datacontext of the mainWindow, like in an answers below, or set ListBox.Datacontext. Hope this will help you.
I'm trying to write a windows phone 8 app using the mvvm pattern but I'm struggling with it.
I have a page with a list of persons which is binded to my PersonViewModel. That part is working fine. I then have 2 buttons in the application bar i.e. add or edit. When I want to edit a person, I select the person from the list, which then sets the CurrentPerson in my ViewModel. This in turns set a property in my MainViewModel which is used to store the currently selected person i.e.
App.MainViewModel.CurrentPerson = this.CurrentPerson;
When I want to add a new person, I use the same principal but I create a new person model.
App.MainViewModel.CurrentPerson = new PersonModel();
I then redirect to a page which contains the fields to handle a person, whether it is being added or edited and this is binded to a ViewModel called PersonEntryViewModel
Before I explain my problem, I want to let you know what I'm trying to achieve. I want the "Save" button in my application bar to get enabled once a certain amount of criteria have been met i.e. Name has been filled and has x characters, etc...
I can see what my problem is but I don't know how to resolve it.
Here is a simplied version of my PersonEntryViewModel:
public class PersonEntryViewModel : BaseViewModel
{
private PersonModel _currentPerson;
private bool _isNewPerson;
private ICommand _savePersonCommand;
private ICommand _cancelCommand;
private ICommand _titleTextChanged;
private bool _enableSaveButton;
public PersonEntryViewModel()
{
this.CurrentPerson = App.MainViewModel.CurrentPerson ?? new PersonModel();
}
public ICommand SavePersonCommand
{
get
{
return this._savePersonCommand ?? (this._savePersonCommand = new DelegateCommand(SavePersonAction));
}
}
public ICommand CancelCommand
{
get
{
return this._cancelCommand ?? (this._cancelCommand = new DelegateCommand(CancelAction));
}
}
public ICommand NameTextChanged
{
get
{
return this._nameTextChanged ?? (this._nameTextChanged = new DelegateCommand(NameTextChangedAction));
}
}
private void NameTextChangedAction(object actionParameters)
{
if (!string.IsNullOrEmpty(this._currentPerson.Name) && _currentPerson.Name.Length > 2)
{
EnableSaveButton = true;
}
}
private void CancelAction(object actionParameters)
{
Console.WriteLine("Cancel");
INavigationService navigationService = this.GetService<INavigationService>();
if (navigationService == null)
return;
navigationService.GoBack();
navigationService = null;
}
private void SavePersonAction(object actionParameters)
{
Console.WriteLine("Saving");
}
public PersonModel CurrentPerson
{
get { return this._currentPerson; }
set
{
if (this._currentPerson != value)
this.SetProperty(ref this._currentPerson, value);
}
}
public string PageTitle
{
get { return this._pageTitle; }
set { if (this._pageTitle != value) this.SetProperty(ref this._pageTitle, value); }
}
public bool IsNewPerson
{
get { return this._isNewPerson; }
set
{
if (this._isNewPerson != value)
{
this.SetProperty(ref this._isNewPerson, value);
if (this._isNewPerson)
this.PageTitle = AppResources.PersonEntryPageNewTitle;
else
this.PageTitle = AppResources.PersonEntryPageEditTitle;
}
}
}
public bool EnableSaveButton
{
get { return this._enableSaveButton; }
set { if (this._enableSaveButton != value) this.SetProperty(ref this._enableSaveButton, value); }
}
}
Here is part of my XAML:
<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{StaticResource PersonEntryViewModel}" >
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" DataContext="{Binding CurrentPerson, Mode=TwoWay}">
<Grid Margin="0,0,0,5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border BorderBrush="{StaticResource PhoneAccentBrush}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="5"
Background="Transparent"
CornerRadius="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="Name:"
Grid.Row="0"
Margin="12,0,0,0"/>
<TextBox Text="{Binding Name, Mode=TwoWay}"
Grid.Row="1">
<!--<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding NameTextChanged, Mode=OneWay}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>-->
</TextBox>
<TextBlock Text="Address:"
Grid.Row="2"
Margin="12,0,0,0"/>
<TextBox Text="{Binding Address, Mode=TwoWay}"
AcceptsReturn="True"
Height="200"
VerticalScrollBarVisibility="Visible"
TextWrapping="Wrap"
Grid.Row="3"/>
As you can see, my layoutRoot grid is binded to my ViewModel i.e. PersonEntryViewModel and the grid content panel containing my textboxes required for editing is binded to CurrentPerson.
Is that the correct way to do it? I need to bind the control to the CurrentPerson property which will contain data if the person is being edited and it will contain a new empty PersonModel if a new person is being added.
As it stands, that part is working. When I type some text in my field and click on the next one, it calls set the CurrentPerson relevant property which in turns calls the PersonModel. Click on the save button and I check the CurrentPerson, I can see it has all the various properties set.
As you can see in my PersonEntryViewModel, I've got other properties which are required. For example the EnableSaveButton, which technically should be set to true or false based on the validation of the various properites from the CurrentPerson object but I need this to be checked as the user is typing text in the various textbox and this is where I'm having a problem.
If I enable the following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding NameTextChanged, Mode=OneWay}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
It doesn't get triggered in the PersonEntryViewModel where I really need it as this is where I want to set my EnableSaveButton property but I guess it makes sense as this code is binded to the Name textbox which is turn is binded to the CurrentPerson property which is my PersonModel.
If I move the code from the PersonEntryViewModel to the PersonViewModel
private ICommand _personTextChanged;
public ICommand PersonTextChanged
{
get
{
return this._personTextChanged ?? (this._personTextChanged = new DelegateCommand(PersonTextChangedAction));
}
}
private void PersonTextChangedAction(object actionParameters)
{
if (!string.IsNullOrEmpty(this._name) && this._name.Length > 2)
{
//EnableSaveButton = true;
Console.WriteLine("");
}
}
It gets triggered accordingly but then how do I get this information back to my PersonEntryViewModel which binded to the view where my 2 buttons (i.e. save & cancel) are located and the EnableSaveButton property is responsible for enabling the save button accordingly when set assuming that the Name is valid i.e. set and minlen is match for example.
Is the PersonEntryViewModel and using a CurrentPerson property with the current person being edited or added designed correctly or not and how am I to handle this scenario?
I hope the above makes sense but if I'm not clear about something, let me know and I'll try to clarify it.
Thanks.
PS: I posted another posted related to how to detect text change, but I figured it out but it's obviously not the problem. The problem seems more related to design.
Your design is unclear to me.
If you want to go with the current design itself, I would suggest you do the following thing.
Remove assigning the DataContext for grid in xaml.
In the code behind add:
var dataContext = new PersonEntryViewModel();
this.ContentPanel.DataContext = dataContext.CurrentPerson;
// After creating App bar
this.appBar.DataContext = dataContext;
//Your xaml code will look something like this:
<AppBar x:Name="appBar">
<Button x:Name="saveBtn" IsEnabled={Binding EnableSaveButton} />
<AppBar />