A noob error for sure (I started yesterday afternoon developing in WP7), but I'm wasting a lot time on it.
I post my class and a little part of my code:
public class ChronoLaps : INotifyPropertyChanged
{
private ObservableCollection<ChronoLap> laps = null;
public int CurrentLap
{
get { return lap; }
set
{
if (value == lap) return;
// Some code here ....
ChronoLap newlap = new ChronoLap()
{
// Some code here ...
};
Laps.Insert(0, newlap);
lap = value;
NotifyPropertyChanged("CurrentLap");
NotifyPropertyChanged("Laps");
}
}
public ObservableCollection<ChronoLap> Laps {
get { return laps; }
set
{
if (value == laps) return;
laps = value;
if (laps != null)
{
laps.CollectionChanged += delegate
{
MeanTime = Laps.Sum(p => p.Time.TotalMilliseconds) / (Laps.Count * 1000);
NotifyPropertyChanged("MeanTime");
};
}
NotifyPropertyChanged("Laps");
}
}
}
MainPage.xaml.cs
public partial class MainPage : PhoneApplicationPage
{
public ChronoLaps History { get; private set; }
private void butStart_Click(object sender, EventArgs e)
{
History = new ChronoLaps();
// History.Laps.Add(new ChronoLap() { Distance = 0 });
LayoutRoot.DataContext = History;
}
}
MainPage.xaml
<phone:PhoneApplicationPage>
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid Grid.Row="2">
<ScrollViewer Margin="-5,13,3,36" Height="758">
<ListBox Name="lbHistory" ItemContainerStyle="{StaticResource ListBoxStyle}"
ItemsSource="{Binding Laps}"
HorizontalAlignment="Left" Margin="5,25,0,0"
VerticalAlignment="Top" Width="444">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Lap}" Width="40" />
<TextBlock Text="{Binding Time}" Width="140" />
<TextBlock Text="{Binding TotalTime}" Width="140" />
<TextBlock Text="{Binding Distance}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
Problem is that when I add one or more items to History.Laps collection, my listbox is not refreshed and these items don't appear.
But if I remove comment on // History.Laps.Add(new ChronoLap()... line, this item appear and so every other inserted later.
More: if I remove that comment and then write History.Laps.Clear() (before or after setting binding) binding is not working anymore. It's like it gets crazy if collection is empty.
I really don't understand the reason...
UPDATE AND SOLUTION:
If i move
History = new ChronoLaps();
LayoutRoot.DataContext = History;
from butStart_Click to public MainPage() everything works as expected.
Can someone explain me the reason?
Actually I see no point of having a separate class for ChronoLaps. Here is how I modified the code for MainPage.xaml.cs and everything seems to be working for me.
public partial class MainPage : PhoneApplicationPage
{
public ObservableCollection<ChronoLap> Laps { get; set; }
public double MeanTime { get; set; }
// Constructor
public MainPage()
{
InitializeComponent();
Laps = new ObservableCollection<ChronoLap>();
Laps.CollectionChanged += delegate
{
MeanTime = Laps.Sum(p => p.Time.TotalMilliseconds) / (Laps.Count * 1000);
};
DataContext = this;
Loaded += (s, e) =>
{
Laps.Add(new ChronoLap() {Time = TimeSpan.FromSeconds(1000)});
Laps.Add(new ChronoLap() {Time = TimeSpan.FromSeconds(1000)});
Laps.Add(new ChronoLap() {Time = TimeSpan.FromSeconds(1000)});
};
}
}
Try binding DataContext and ItemSource for listbox..
How i have done is..
<ListBox x:Name="AppList" Background="White" DataContext="{Binding DisplayItem}" SelectionChanged="AppList_SelectionChanged" Height="500" Width="auto">
<ListBox.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And i dont know if it will help but still i will just post the code that i am using..
ItemList.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
namespace AppHouseLibrary
{
public class ItemList : IComparable
{
private string _name;
public string WidgetName
{
get
{
return _name;
}
set
{
_name = value;
}
}
public int ID
{
get;
set;
}
private BitmapImage _Icon;
public BitmapImage Icon
{
get
{
return _Icon;
}
set
{
_Icon = value;
}
}
//public string arrow { get; set; }
public BitmapImage arrow { get; set; }
public int CompareTo(ItemList other)
{
return this.WidgetName.CompareTo(other.WidgetName);
}
}
}
I have a UIManager.cs class in which am refreshing the data before i load it on the UI to the user..
using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Media.Imaging;
using System.Collections.Generic;
using StirLibrary.com.mportal.data.bean;
using com.mportal.utils;
using StirLibrary.com.mportal.utils;
namespace StirLibrary.com.UI
{
public class UIManager : INotifyPropertyChanged
{
private static UIManager instance = null;
private static string TAG = "UIManager";
BitmapImage arrowImage = Utils.returnImage(ImageUtils.ARROW);
public List<ItemList> data = new List<ItemList>();
public static UIManager getInstance()
{
if (instance == null)
{
instance = new UIManager();
}
return instance;
}
private ObservableCollection<ItemList> _displayItem = new ObservableCollection<ItemList>();
public ObservableCollection<ItemList> DisplayItem
{
get
{
return _displayItem;
}
}
private UIManager()
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String PropertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public WidgetBean[] serviceBeanList = null;
public WidgetBean[] wheelBeanList = null;
public WidgetBean getServiceWidgetBean(int selectedIndex)
{
try
{
if (serviceBeanList != null)
{
return serviceBeanList[selectedIndex];
}
}
catch (Exception e)
{
Logger.log(TAG, e.Message);
}
return null;
}
public WidgetBean getWheelWidgetBean(int selectedIndex)
{
try
{
if (wheelBeanList != null)
{
return wheelBeanList[selectedIndex];
}
}
catch (Exception e)
{
Logger.log(TAG, e.Message);
}
return null;
}
public void DisplayCatalog(string[] ServiceDisplayName, string[] WheelDisplayName, BitmapImage[] ServiceIcons, WidgetBean[] ServiceBeanList, WidgetBean[] WheelBeanList)
{
try
{
DisplayItem.Clear();
string disp1 = string.Empty;
string name = ServiceDisplayName[0];
wheelBeanList = WheelBeanList;
serviceBeanList = ServiceBeanList;
for (int i = 0; i < ServiceDisplayName.Length; i++)
{
WidgetBean bean = serviceBeanList[i];
if (bean.isCategory())
{
DisplayItem.Add(new ItemList { WidgetName = ServiceDisplayName[i], Icon = ServiceIcons[i], arrow = arrowImage });
}
else
{
DisplayItem.Add(new ItemList { WidgetName = ServiceDisplayName[i], Icon = ServiceIcons[i] });
}
}
NotifyPropertyChanged("UI");
}
catch (Exception e)
{
Logger.log(TAG,e.Message);
}
}
public void DisplayCatalog(string[] displayName, BitmapImage[] icons, WidgetBean[] beanArray)
{
try
{
serviceBeanList = beanArray;
DisplayItem.Clear();
for (int i = 0; i < displayName.Length; i++)
{
WidgetBean bean = serviceBeanList[i];
if (bean.isCategory())
{
DisplayItem.Add(new ItemList { WidgetName = displayName[i], Icon = icons[i], arrow = arrowImage });
}
else
{
DisplayItem.Add(new ItemList { WidgetName = displayName[i], Icon = icons[i] });
}
}
NotifyPropertyChanged("UI");
}
catch (Exception e)
{
Logger.log(TAG,e.Message);
}
}
}
}
Related
I am coding a Xamarin.Forms project and I have a list view but whenever I show hidden content, for example, make an entry visible it the ViewCell overlaps the one beneath it.
Is there a way I could .Update() the listview or something to refresh it and make them all fit.
I don't want the refresh to cause it to go back to the top though.
Android seems to be able to automatically update the height when I show something.
I tried using HasUnevenRows="True" but that still didn't fix it.
Code:
Message.xaml
<StackLayout>
<local:PostListView x:Name="MessageView" HasUnevenRows="True" IsPullToRefreshEnabled="True" Refreshing="MessageView_Refreshing" SeparatorVisibility="None" BackgroundColor="#54a0ff">
<local:PostListView.ItemTemplate>
<DataTemplate>
<local:PostViewCell>
<StackLayout>
<Frame CornerRadius="10" Padding="0" Margin="10, 10, 10, 5" BackgroundColor="White">
<StackLayout>
<StackLayout x:Name="MessageLayout" BackgroundColor="Transparent" Padding="10, 10, 15, 10">
...
<Label Text="{Binding PostReply}" FontSize="15" TextColor="Black" Margin="10, 0, 0, 10" IsVisible="{Binding ShowReply}"/>
<StackLayout Orientation="Vertical" IsVisible="{Binding ShowReplyField}" Spacing="0">
<Entry Text="{Binding ReplyText}" Placeholder="Reply..." HorizontalOptions="FillAndExpand" Margin="0, 0, 0, 5"/>
...
</StackLayout>
<StackLayout x:Name="MessageFooter" Orientation="Horizontal" IsVisible="{Binding ShowBanners}">
<StackLayout Orientation="Horizontal">
...
<Image x:Name="ReplyIcon" Source="reply_icon.png" HeightRequest="20" HorizontalOptions="StartAndExpand" IsVisible="{Binding ShowReplyButton}">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ReplyClick}" CommandParameter="{Binding .}"/>
</Image.GestureRecognizers>
</Image>
...
</StackLayout>
...
</StackLayout>
</StackLayout>
</StackLayout>
</Frame>
</StackLayout>
</local:PostViewCell>
</DataTemplate>
</local:PostListView.ItemTemplate>
</local:PostListView>
</StackLayout>
Message.cs
using Newtonsoft.Json;
using SocialNetwork.Classes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace SocialNetwork
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MessagePage : ContentPage
{
public MessagePage()
{
InitializeComponent();
LoadPage();
}
private async void LoadPage()
{
await LoadMessages();
}
private async void RefreshPage()
{
await LoadMessages();
MessageView.EndRefresh();
}
private async Task LoadMessages()
{
//*Web Request*
MessageView.ItemsSource = FormatPosts(this, Navigation, page_result);
...
}
public IList<MessageObject> FormatPosts(Page page, INavigation navigation, string json)
{
IList<MessageObject> Posts = new List<MessageObject>() { };
var messages = JsonConvert.DeserializeObject<List<Message>>(json);
foreach (var message in messages)
{
MessageObject mo = MessageObject.CreateMessage(...);
Posts.Add(mo);
}
return Posts;
}
public async void ShowOptionActions(string id, string poster_id, object message)
{
...
}
public async void ShowReportOptions(string id, string poster_id)
{
...
}
public void SubmitReplyClick(string id, object msg)
{
...
}
public async void SendReplyAsync(string id, object msg, string reply)
{
await SendReply(id, msg, reply);
}
public void ReplyCommandClick(string id, object msg)
{
MessageObject message = (MessageObject) msg;
message.ShowReplyField = message.ShowReplyField ? false : true;
//Update Cell Bounds
}
private async Task SendReply(string id, object msg, string reply)
{
MessageObject message = (MessageObject)msg;
...
message.PostReply = reply;
//Update Cell Bounds
}
public async void LikeMessageClick(string id, object message)
{
await LikeMessage(id, message);
}
private async Task LikeMessage(string id, object msg)
{
...
}
public async void DeleteMessage(string id, object msg)
{
MessageObject message = (MessageObject)msg;
message.ShowBanners = false;
message.ShowReply = false;
...
//Update Cell Bounds
}
public async Task ReportMessage(...)
{
...
}
private void MessageView_Refreshing(object sender, EventArgs e)
{
RefreshPage();
}
}
public class MessageObject : INotifyPropertyChanged
{
private Boolean showBannersValue = true;
private string replyValue = String.Empty;
private bool showReplyValue;
private bool showReplyButtonValue;
private bool showReplyFieldValue;
private Command replyCommandValue;
private Command replySubmitValue;
private string replyTextValue;
...
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private MessageObject(...)
{
...
}
public static MessageObject CreateMessage(...)
{
return new MessageObject(...);
}
public Boolean ShowBanners
{
get
{
return this.showBannersValue;
}
set
{
if (value != this.showBannersValue)
{
this.showBannersValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReplyField
{
get
{
return this.showReplyFieldValue;
}
set
{
if(value != this.showReplyFieldValue)
{
this.showReplyFieldValue = value;
NotifyPropertyChanged();
}
}
}
public string PostReply
{
get
{
return this.replyValue;
}
set
{
if (value != this.replyValue)
{
this.replyValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReply
{
get
{
return this.showReplyValue;
}
set
{
if(value != this.showReplyValue)
{
this.showReplyValue = value;
NotifyPropertyChanged();
}
}
}
public Boolean ShowReplyButton
{
get
{
return this.showReplyButtonValue;
}
set
{
if (value != this.showReplyButtonValue)
{
this.showReplyButtonValue = value;
NotifyPropertyChanged();
}
}
}
public string ReplyText
{
get
{
return this.replyTextValue;
}
set
{
if(value != this.replyTextValue)
{
this.replyTextValue = value;
NotifyPropertyChanged();
}
}
}
public Command ReplyClick
{
get
{
return this.replyCommandValue;
}
set
{
if (value != this.replyCommandValue)
{
this.replyCommandValue = value;
NotifyPropertyChanged();
}
}
}
...
}
}
Save your IList<MessageObject> which gets returned from your FormatPosts method in a field IList<MessageObject> _messages = new List<MessageObject>()
And use the following snippet to update the ListView whenever you need, includes a check to see if the device runs on iOS:
if(Device.RuntimePlatform == Device.iOS)
{
MessageView.ItemsSource = null;
MessageView.ItemsSource = _messages;
}
Especially with iOS there are issues resizing rows in a ListView according to changes of cells (see here). There is a method ForceUpdateSize on Cell, which should notify the ListView that the size of the cell has changed, which should cause the ListView to resize its rows.
Oh, I faced the same thing.
I guess that you just need to add this somewhere in your listview:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.row='0'>
...
</Grid>
// This, in my case, makes my cell expand when it's true. Normal behavior
<Grid Grid.row='1' isVisible="{Binding Expand}">
...
</Grid>
</Grid>
Plus, if you want to update cells individually, I use a CustomObservableCollection:
public class CustomObservableCollection<T> : ObservableCollection<T>
{
public CustomObservableCollection() { }
public CustomObservableCollection(IEnumerable<T> items) : this()
{
foreach(var item in items)
this.Add(item);
}
public void ReportItemChange(T item)
{
NotifyCollectionChangedEventArgs args =
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace,
item,
item,
IndexOf(item));
OnCollectionChanged(args);
}
}
With a Custom ListView to do ItemClickCommand:
public class CustomListView : ListView
{
#pragma warning disable 618
public static BindableProperty ItemClickCommandProperty = BindableProperty.Create<CustomListView, ICommand>(x => x.ItemClickCommand, null);
#pragma warning restore 618
public CustomListView(ListViewCachingStrategy cachingStrategy = ListViewCachingStrategy.RetainElement) :
base(cachingStrategy)
{
this.ItemTapped += this.OnItemTapped;
}
public ICommand ItemClickCommand
{
get { return (ICommand)this.GetValue(ItemClickCommandProperty); }
set { this.SetValue(ItemClickCommandProperty, value); }
}
private void OnItemTapped(object sender, ItemTappedEventArgs e)
{
if(e.Item != null && this.ItemClickCommand != null && this.ItemClickCommand.CanExecute(e.Item))
{
this.ItemClickCommand.Execute(e.Item);
this.SelectedItem = null;
}
}
}
then in xaml:
...
...
<Customs:CustomListView
HasUnevenRows="true"
ItemsSource="{Binding PersonList}"
IsPullToRefreshEnabled="True"
RefreshCommand="{Binding DoRefreshCommand}"
ItemClickCommand="{Binding ItemClickCommand}">
...
...
</Customs:CustomListView>
Finally:
public Command<Person> ItemClickCommand { get; set; }
...
ItemClickCommand = new Command<Person>(SelectionExecute);
...
private void SelectionExecute(Person arg)
{
arg.Expand = !arg.Expand;
foreach(var item in PersonList)
{
if(item.Key == arg.Id)// you will change this probably
item.ReportItemChange(arg);
}
}
Hope it help a bit :)
I'm trying to make good use of ReactiveList and I think I'm close.
My expectation is that only "toyota" is shown after the user presses the filter button
XAML (yes, quick n dirty, no command for the Filter)
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<StackPanel>
<ComboBox
ItemsSource="{Binding Path=CarsVM}"
DisplayMemberPath="Name" />
<Button
Click="ButtonBase_OnClick">
Filter
</Button>
</StackPanel>
</Window>
The code
using System.Windows;
using ReactiveUI;
namespace WpfApplication1
{
public partial class MainWindow
{
private readonly ViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new ViewModel();
DataContext = _viewModel;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
_viewModel.ChangeFilter();
}
}
}
public class CarViewModel : ReactiveObject
{
private bool _isVisible = true;
public CarViewModel(string name)
{
Name = name;
}
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
this.RaiseAndSetIfChanged(ref _isVisible, value);
}
}
public string Name { get; set; }
}
public class ViewModel
{
private readonly ReactiveList<CarViewModel> _cars = new ReactiveList<CarViewModel>
{
new CarViewModel("bmw"),
new CarViewModel("toyota"),
new CarViewModel("opel")
};
public ViewModel()
{
_cars.ChangeTrackingEnabled = true;
CarsVM = _cars.CreateDerivedCollection(x => x, x => x.IsVisible);
}
public IReactiveDerivedList<CarViewModel> CarsVM { get; set; }
public void ChangeFilter()
{
foreach (var car in _cars)
{
car.IsVisible = car.Name.Contains("y");
}
}
}
Your bug is in the setter of IsVisible. By pre-assigning the value of _isVisible, RaiseAndSetIfChanged always thinks that the value has never changed. Remove _isVisible = value; and everything should work.
Hi i'm new using MVVM and i'm trying to binding a listbox but it doesn't work. Here's my code
Model
public class Musicmodel : INotifyPropertyChanged
{
//variables privadas
private String _artista;
private Uri _href;
private String _informacion;
private Double _Dvalue;
public String artista
{
get
{
return this._artista;
}
set
{
this._artista= value;
this.RaisePropertyChanged("artista");
}
}
public Uri href {
get {
return this._href;
}
set
{
this._href = value;
this.RaisePropertyChanged("href");
}
}
public String informacion {
get
{
return this._informacion;
}
set
{
this._informacion = value;
this.RaisePropertyChanged("informacion");
}
}
public Double Dvalue
{
get
{
return this._Dvalue;
}
set
{
this._Dvalue = value;
this.RaisePropertyChanged("Dvalue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
ViewModel
public class DownloadFileViewModel : INotifyPropertyChanged
{
private WebClient clienteDownload;
private ObservableCollection<Model.Music>_musicSource= new ObservableCollection<Model.Music>();
public ObservableCollection<Model.Music> musicSource
{
get
{
return this._musicSource;
}
set
{
this._musicSource = value;
RaisePropertyChanged("musicSource");
}
}
private int index = 0;
//request para descargar la canción
public void request(Model.Musicmodel item)
{
this.clienteDownload = new WebClient();
this.clienteDownload.DownloadProgressChanged += new DownloadProgressChangedEventHandler(clienteDownload_DownloadProgressChanged);
//agregamos el item al music
this.musicSource.Add(item);
this.clienteDownload.OpenReadAsync(this.musicSource[index].href);
}
private void clienteDownload_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.musicSource[index].Dvalue=(double)e.ProgressPercentage;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
View
<ListBox x:Name="list" ItemsSource="{Binding musicSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding artista}"/>
<ProgressBar Value="{Binding Dvalue}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code Behind
protected override void OnNavigatedTo(NavigationEventArgs e)
{
DownloadFileViewModel download = new DownloadFileViewModel();
Model.Music newMusic = new Model.Music() { href = new Uri("http://media1.li.ru/b/4/mp3/2/95366/953662_14Friday_Im_In_Love.mp3", UriKind.Absolute), artista = "the cure" };
download.request(newMusic);
this.DataContext = download;
base.OnNavigatedTo(e);
}
I've debuged this and the download works fine and my ObservableCollection fills correctly whithout any problem but when i try to binding my listbox fails.
please what do i'm doing wrong?
thanks
The problem is quite simple. You initialize your musicSource property at the begining in
private ObservableCollection<Model.Music>_musicSource= new ObservableCollection<Model.Music>();
And then just add stuff to it after the request completes. The RaiseProperyChanged("Property") will only fire when you add a new observable collection but not when you add items to it.
Add this line again to the end of the request (when you populate your musicSource)
RaisePropertyChanged("musicSource");
This will trigger another update in the view
EDIT:
Another approach is to have an additional field like
private ObservableCollection<Model.Music>_anotherMusicSource= new ObservableCollection<Model.Music>();
And do everything on it and after that just say:
musicSource = _anotherMusicSource;
This will then trigger the notification and everything should work
You have an underscore in your property name
private ObservableCollection<Model.Musicmodel> musicSource= new ObservableCollection<Model.Musicmodel>();
public ObservableCollection<Model.Musicmodel> _musicSource
{
get
{
return this.musicSource;
}
set
{
this.musicSource = value;
RaisePropertyChanged("musicSource");
}
}
You have this mixed up - the underscore should (traditionally) be on the private member, not the public - your binding is targeting musicSource which is private
The standard convention which is advocated for .NET is:
// Private member variables
private int _someInteger;
// Public ones
public int SomeInteger { get { ... } set { ... } }
This is my class:
[Table]
public class ListData : INotifyPropertyChanged, INotifyPropertyChanging
{
private int _id;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int Id
{
get
{
return _id;
}
set
{
if (_id != value)
{
OnPropertyChanging("Id");
_id = value;
OnPropertyChanged("Id");
}
}
}
private string _name;
[Column()]
public string Name
{
get
{ return _name; }
set
{
if (_name != value)
{
OnPropertyChanging("Name");
_name = value;
OnPropertyChanged("Name");
}
}
}
private string _url;
[Column()]
public string Url
{
get
{
return _url;
}
set
{
if (_url != value)
{
OnPropertyChanging("Url");
_url = value;
OnPropertyChanged("Url");
}
}
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
private void OnPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
}
public class ListDataContext : DataContext
{
public static string DBConnectionString = "Data Source=isostore:/ListDataDB.sdf";
public ListDataContext(string sConnectionString)
: base(sConnectionString)
{ }
public Table<ListData> Datas;
}
this is part of my xaml (listbox):
<ListBox x:Name="listData" Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Path=Name}"
Style="{StaticResource PhoneTextTitle2Style}"
x:Name="txbName"
Tap="txbName_Tap">
</TextBlock>
<Button Tag="{Binding BindsDirectlyToSource=True}"
Click="Button_Click"
Content="X"
BorderBrush="Red"
Background="Red"
Foreground="{StaticResource PhoneBackgroundBrush}"
VerticalAlignment="Top" BorderThickness="0" Width="70" Height="70">
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
here is my methods for loading data:
public void LoadData()
{
this.listData.ItemsSource = GetData();
}
public ObservableCollection<ListData> GetData()
{
List<ListData> listDatas = new List<ListData>();
using (var db = new ListDataContext(ConnectionString))
{
var query = from e in db.Datas
select e;
listDatas = query.ToList();
}
return new ObservableCollection<ListData>(listDatas);
}
and this is how I delete them:
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
var data = (ListData)((Button)sender).Tag;
using (var db = new ListDataContext(ConnectionString))
{
var entities = from i in db.Datas
where i.Name == data.Name
select i;
var entity = entities.FirstOrDefault();
db.Datas.DeleteOnSubmit(entity);
db.SubmitChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
What´s the problem? Why isn't my ListBox refreshing after deleting? Is there some problem with ObservableCollection?
You are removing it from the database, but I don't see where you call GetData() again, or remove it from the ObservableCollection. Try calling GetData after you delete, or at least manually remove the selected item from the collection.
Validation error state doesn't get reflected in my UserControl. I was following this similar StackOverflow question to solve it. I am not using an MVVM approach by choice. It's using WCF RIA Services with Entity Framework.
But it didn't seem to help me, what am I missing, or why is my scenario different?
Note: If I put a TextBox (not inside the UserControl) in my main page, it shows validation error.
This is my UserControl code:
<UserControl x:Class="TestApp.MyUserControl"
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" d:DesignHeight="25" d:DesignWidth="120">
<Grid x:Name="LayoutRoot" Background="White">
<TextBox x:Name="TextBox" />
</Grid>
</UserControl>
and this is the code behind of the UserControl:
public partial class MyUserControl : UserControl, INotifyDataErrorInfo
{
public MyUserControl()
{
InitializeComponent();
this.TextBox.BindingValidationError += MyUserControl_BindingValidationError;
Loaded += MyUserControl_Loaded;
this.TextBox.Unloaded += MyUserControl_Unloaded;
}
private void MyUserControl_Loaded(object sender, RoutedEventArgs e)
{
this.TextBox.SetBinding(TextBox.TextProperty,
new Binding()
{
Source = this,
Path = new PropertyPath("Value"),
Mode = BindingMode.TwoWay,
NotifyOnValidationError = false,
ValidatesOnExceptions = true,
ValidatesOnDataErrors = true,
ValidatesOnNotifyDataErrors = true
});
}
private void MyUserControl_Unloaded(object sender, RoutedEventArgs e)
{
this.TextBox.ClearValue(TextBox.TextProperty);
}
public static DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(string), typeof(MyUserControl),
new PropertyMetadata(null));
public static void ValuePropertyChangedCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
((MyUserControl)dependencyObject).NotifyErrorsChanged("Value");
}
public string Value
{
get { return ((string)base.GetValue(ValueProperty)).Trim(); }
set { base.SetValue(ValueProperty, string.IsNullOrEmpty(value) ? " " : value.Trim()); }
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
IEnumerable returnValue = null;
var errorMessage = "";
if (propertyName == "Value")
{
if (Validation.GetErrors(this).Count == 0)
{
errorMessage = "";
}
else
{
errorMessage = Validation.GetErrors(this).First().ErrorContent.ToString();
}
if (String.IsNullOrEmpty(errorMessage))
{
returnValue = null;
}
else
{
returnValue = new List<String> { errorMessage };
}
}
return returnValue;
}
public bool HasErrors
{
get { return Validation.GetErrors(this).Any(); }
}
private void MyUserControl_BindingValidationError(object sender, System.Windows.Controls.ValidationErrorEventArgs e)
{
this.NotifyErrorsChanged("Value");
}
public void NotifyErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
{
ErrorsChanged(this, new System.ComponentModel.DataErrorsChangedEventArgs(propertyName));
}
}
}
I am using it like this in my main page:
<my:MyUserControl x:Name="UC"
Value="{Binding Path=Days, Mode=TwoWay,
NotifyOnValidationError=True,
ValidatesOnNotifyDataErrors=True}" />
I am also using validation attributes in System.ComponentModel.DataAnnotations, this is how it looks in RIAService.metadata.cs class:
internal sealed class tblRiskRegisterMetadata
{
//Metadata classes are not meant to be instantiated.
private tblRiskRegisterMetadata()
{ }
[Range(0, 1000, ErrorMessage = "Days should be 0-100")]
public int Days{ get; set; }
}
new PropertyMetadata(null) -> new PropertyMetadata(null, **ValuePropertyChangedCallback**)