I know there are some similar topics here but I couldn't get any answer from them. I have to update background of a grid to either an image or a colour in my Windows Phone 7 app. I do this using my value converter , it works fine but I'd have to reload the collection so it updates the colour or image.
<Grid Background="{Binding Converter={StaticResource ImageConverter}}" Width="125" Height="125" Margin="6">
The converter receives the object then gets the color and image from it, here is the converter
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
People myC = value as People;
string myImage = myC.Image;
object result = myC.TileColor;
if (myImage != null)
{
BitmapImage bi = new BitmapImage();
bi.CreateOptions = BitmapCreateOptions.BackgroundCreation;
ImageBrush imageBrush = new ImageBrush();
using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (myIsolatedStorage.FileExists(myImage))
{
using (
IsolatedStorageFileStream fileStream = myIsolatedStorage.OpenFile(myImage, FileMode.Open,
FileAccess.Read))
{
bi.SetSource(fileStream);
imageBrush.ImageSource = bi;
}
}
else
{
return result;
}
}
return imageBrush;
}
else
{
return result;
}
}
I need to somehow update/refresh the grid tag or the value converter so that it can show the latest changes !
EDIT
ADDED SOME MORE CODE
The model :
[Table]
public class People : INotifyPropertyChanged, INotifyPropertyChanging
{
private int _peopleId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int PeopleId
{
get { return _peopleId; }
set
{
if (_peopleId != value)
{
NotifyPropertyChanging("PeopleId");
_peopleId = value;
NotifyPropertyChanged("PeopleId");
}
}
}
private string _peopleName;
[Column]
public string PeopleName
{
get { return _peopleName; }
set
{
if (_peopleName != value)
{
NotifyPropertyChanging("PeopleName");
_peopleName = value;
NotifyPropertyChanged("PeopleName");
}
}
}
private string _tileColor;
[Column]
public string TileColor
{
get { return _tileColor; }
set
{
if (_tileColor != value)
{
NotifyPropertyChanging("TileColor");
_tileColor = value;
NotifyPropertyChanged("TileColor");
}
}
}
private string _image;
[Column]
public string Image
{
get { return _image; }
set
{
if (_image != value)
{
NotifyPropertyChanging("Image");
_image = value;
NotifyPropertyChanged("Image");
}
}
}
[Column]
internal int _groupId;
private EntityRef<Groups> _group;
[Association(Storage = "_group", ThisKey = "_groupId", OtherKey = "Id", IsForeignKey = true)]
public Groups Group
{
get { return _group.Entity; }
set
{
NotifyPropertyChanging("Group");
_group.Entity = value;
if (value != null)
{
_groupId = value.Id;
}
NotifyPropertyChanging("Group");
}
}
[Column(IsVersion = true)]
private Binary _version;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
ViewModel :
public class PeopleViewModel : INotifyPropertyChanged
{
private PeopleDataContext PeopleDB;
// Class constructor, create the data context object.
public PeopleViewModel(string PeopleDBConnectionString)
{
PeopleDB = new PeopleDataContext(PeopleDBConnectionString);
}
private ObservableCollection<People> _allPeople;
public ObservableCollection<People> AllPeople
{
get { return _allPeople; }
set
{
_allPeople = value;
NotifyPropertyChanged("AllPeople");
}
}
public ObservableCollection<People> LoadPeople(int gid)
{
var PeopleInDB = from People in PeopleDB.People
where People._groupId == gid
select People;
AllPeople = new ObservableCollection<People>(PeopleInDB);
return AllPeople;
}
public void updatePeople(int cid, string cname, string image, string tilecol)
{
People getc = PeopleDB.People.Single(c => c.PeopleId == cid);
getc.PeopleName = cname;
getc.Image = image;
getc.TileColor = tilecol;
PeopleDB.SubmitChanges();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Application Page
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Margin="0,8,0,0" x:Name="Peoplelist" HorizontalAlignment="Center" BorderThickness="4" ItemsSource="{Binding AllPeople}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="{Binding Converter={StaticResource ImageConverter}}" Width="125" Height="125" Margin="6">
<TextBlock Name="name" Text="{Binding PeopleName}" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center" TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
Application Page code behind
public partial class PeopleList : PhoneApplicationPage
{
private int gid;
private bool firstRun;
public PeopleList()
{
InitializeComponent();
firstRun = true;
this.DataContext = App.ViewModel;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
gid = int.Parse(NavigationContext.QueryString["Id"]);
if (firstRun)
{
App.ViewModel.LoadPeople(gid);
firstRun = false;
}
}
}
Background="{Binding Converter={StaticResource ImageConverter}}"
suggests you bind directly toPeople
item (which is your problem when refreshing).So, you should rearrange a bit. Make that
a property
of some other 'higher' data context instead.How to rearrange things:
1) your 'model' (entity from db) should be different than your view-model. To avoid going into details, it solves lot of problems - e.g. like you're having. The
People
getters/setters are not normally overriden in that way (EF often uses reflection to deal w/ entities etc.).So, make PeopleVM (for single People or
PersonViewModel
) - copy things in there - and make INotify in there - leave People just a pure entity/poco w/ automatic get/set.2) Same for the
PeopleViewModel
- it's too tied-up to the Db (these are also design guidelines).You shouldn't reuse the DbContext - don't save it - it's a 'one off' object (and cached inside) - so use
using()
to deal with and load/update on demand.3) Replace People in your main VM with PersonViewModel. When you load from db, pump up into the PersonVM first - when you're saving the other way around. That's a tricky bit with MVVM, you often need to copy/duplicate - you could use some tool for that to automate or just make copy ctor-s or something.
Your
ObservableCollection<People> AllPeople
becomesObservableCollection<PersonViewModel> AllPeople
4) XAML - your binding AllPeople, PeopleName is the same - but that points now to view-models (and Name to VM Name).
But you should bind your
grid
to something other thanPersonViewModel
(old People) - as that is hard to 'refresh' when inside the collection.a) Make a new single property like
ImageAndTileColor
- and make sure it updates/notifies on / when any of the two properties change.b) The other option is to use
MultiBinding
- and bind 2, 3 properties - one being the wholePersonViewModel
like you have and plus those other two properties - e.g...That way you force the binding to refresh when any of the 3 changes - and you still have your full People in there (actually you could just use two as all you need is Image and TileColor).
5) Change your Converter to be IMultiValue... and to read multiple values sent in.
That's all :)
SHORT VERSION:
That was the
proper way
and certain to work (it depends on how/when you update Person properties etc.) - but you could try theshort version
first - just do themulti-binding
part on the People model - and hope it'd work. If it doesn't you have to do all the above.Windows Phone 7:
Since there is no
MultiBinding
...- Use the workaround - it should be quite similar,
- Or go with the
(a)
above - bind grid to{Binding ImageAndTileColor, Converter...}
. Make new property (you can do the same if you wish in the entity/model - just mark it as[NotMapped()]
) which would be a 'composite' one.http://www.thejoyofcode.com/MultiBinding_for_Silverlight_3.aspx
I got it (Thanks to NSGaga). I set his post as the answer, the following is what I did
First I needed to make the converter to receive PeopleId instead of the object itself
Then I just had to add call
NotifyPropertyChanged("PeopleId")
whenever I update Image or TileColor like thisThis forces the value converter to refresh :)