How can I implement INotifyPropertyChanged in WPF with MVP pattern - c#

Good morning everyone, I have a sensor model class that, when the state changes, starts a timer that measures random values ​​at intervals. How can I change the measured values ​​on the main form with the same interval?
This is my window xaml markup
<ListBox Width="100" Name="Lst" Grid.Column="0" Grid.Row="1" SelectionChanged="Lst_OnSelectionChanged"/>
<Label Width="244" Style="{StaticResource SideText}" x:Name ="LavalIntervalValue" Grid.Row="1" Grid.Column="1" Margin="215,18,0,344" Height="40"/>
<Label Width="244" Style="{StaticResource SideText}" x:Name ="LabelIdValue" Grid.Row="1" Grid.Column="1" Margin="215,73,0,289" Height="40"/>
<Label Width="244" Style="{StaticResource SideText}" x:Name ="LabelStateValue" Grid.Row="1" Grid.Column="1" Margin="215,130,0,232" Height="40"/>
<Label Width="244" Style="{StaticResource SideText}" x:Name ="LabelMeasuredValue" Grid.Row="1" Grid.Column="1" Margin="215,188,0,174" Height="40" />
<Label Width="186" Style="{StaticResource MainText}" Content="Interval" x:Name ="LabelInterval" Grid.Row="1" Grid.Column="1" Margin="11,18,663,344" Height="40"/>
<Label Width="187" Style="{StaticResource MainText}" Content="Id" x:Name ="LabelId" Grid.Row="1" Grid.Column="1" Margin="10,73,663,289" Height="40"/>
<Label Width="186" Style="{StaticResource MainText}" Content="State" x:Name ="LabelState" Grid.Row="1" Grid.Column="1" Margin="11,130,663,233" Height="39"/>
<Label Width="186" Style="{StaticResource MainText}" Content="Measured Value" x:Name ="LabelMeasured" Grid.Row="1" Grid.Column="1" Margin="11,188,663,175" Height="39"/>
This is my model
public class Sensor:IObservable
{
private int _measuredValue;
private List<IObserver> _observers = new List<IObserver>();
private IMeasuringState _state = new SimpleState();
public Sensor(int measuringInterval, SensorType sensorType, int uniqueId)
{
MeasuringInterval = measuringInterval;
SensorType = sensorType;
UniqueId = uniqueId;
}
[JsonIgnore]
public IMeasuringState MeasuringState
{
get => _state;
set
{
if (value!=null)
{
_state = value;
}
NotifyObservers();
}
}
public int UniqueId { get; }
public int MeasuredValue
{
get => _measuredValue;
set
{
_measuredValue = value;
NotifyObservers();
}
}
public SensorType SensorType { get; }
public int MeasuringInterval { get; }
public void ChangeState()
{
MeasuringState.Handle(this);
MeasuringState.StartMeasure(this);
}
}
Calibration state
public void StartMeasure(Sensor sensor)
{
_calibrationValue = 0;
_timer = new Timer(SetCalibration, sensor, 0, 1000);
}
public void Handle(Sensor sensor)
{
_timer.Dispose();
sensor.MeasuringState = new WorkState();
}
private void SetCalibration(object obj)
{
var sensor = obj as Sensor;
sensor.MeasuredValue = ++_calibrationValue;
}
This is my interface that I implemented in my main window
In property I set my label content value with presenter.
public interface IMainWindowView
{
List<string> SensorsType { get; set; }
int SelectedSensor { get; set; }
int MeasuredValue { get; set; }
string State { get; set; }
int Id { get; set; }
int MeasuringInterval { get; set; }
event EventHandler<EventArgs> OpenFile;
event EventHandler<StateEventArgs> DeleteSensorById;
event EventHandler<EventArgs> SaveFile;
event EventHandler<EventArgs> SelectedItem;
event EventHandler<StateEventArgs> ChangeStateSensor;
event EventHandler<EventArgs> OpenAddWindow;
void ShowWarning(string message);
}

Related

Xamarin.Forms reload a collectionview

I am using Xamarin Forms and I have created a collection view, I am getting the data from an api after getting the device location, my question is how can I reload the collectionview? Here is my code:
public partial class HomePage : ContentPage
{
APIClass aPIClass = new APIClass();
public HomePage()
{
InitializeComponent();
getLocation();
}
public async void getLocation()
{
try
{
var location = await Geolocation.GetLastKnownLocationAsync();
if (location != null)
{
HomeClass places = aPIClass.getUserPlace(location.Latitude.ToString(), location.Longitude.ToString());
HomeCollection.ItemsSource = places.results;
Console.WriteLine($"Latitude: {location.Latitude}, Longitude: {location.Longitude}, Altitude: {location.Altitude}");
}
}
catch(Exception e)
{
Console.WriteLine(e.Message.ToString());
}
}
}
And my view:
<CollectionView x:Name="HomeCollection">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="150" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<BoxView Color="White" Grid.Column="1" Grid.RowSpan="2"/>
<ContentView Padding="10" Grid.Row="0" Grid.Column="1">
<Image Grid.Row="0" Grid.Column="1" Source="{Binding photos[0].name}" Aspect="AspectFill" />
</ContentView>
<ContentView Padding="10" Grid.Row="1" Grid.Column="1">
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding name}" />
</ContentView>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Classes involved:
public class HomeClass
{
public List<PlacesClass> results { get; set; }
}
public class PlacesClass
{
public string name { get; set; }
public List<PlacesImageClass> photos { get; set; }
}
public class PlacesImageClass
{
public int height { get; set; }
public List<string> html_attributions { get; set; }
public string photo_reference { get; set; }
public int width { get; set; }
public string name { get; set; }
}

Xamarin Forms ListView Bindings don't get updated

I have a problem. I created a ListView with a ViewModel. In my ListView I have a few Labels with Text that is bound to the objects in the ItemSource. Now when I change a value in the ViewModel of an item in the ObservableCollection, nothing changes on the screen!
Here is my ViewModel:
public class VM_DeviceList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand cmdDeleteDevice
{
get
{
return new Command<int>((x) => DeleteDevice_Handler(x));
}
}
public ICommand cmdTogglePower
{
get
{
return new Command<int>((x) => TogglePower_Handler(x));
}
}
private ObservableCollection<DisplayedDevice> _knownDeviceList;
public ObservableCollection<DisplayedDevice> knownDeviceList
{
get
{
return _knownDeviceList;
}
set
{
if (_knownDeviceList != value)
{
_knownDeviceList = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("knownDeviceList"));
}
}
}
}
Here is my class for the ObservableCollection:
public class DisplayedDevice : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int Id { get; set; }
public string Name { get; set; }
public string State { get; set; }
public string StateShown { get; set; }
public string deviceImage { get; set; }
public string Color { get; set; }
public string PowerStateColor { get; set; }
public string DeviceImageColor { get; set; }
public string DeviceImage
{
get
{
return deviceImage;
}
set
{
deviceImage = value;
OnPropertyChanged();
}
}
}
And here is the xaml:
<ListView ItemsSource="{Binding knownDeviceList}" SelectionMode="None" RowHeight="90" ItemTapped="device_Clicked" x:Name="MyListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<AbsoluteLayout HeightRequest="70" Margin="20,10,20,10">
<StackLayout Opacity="0.3" BackgroundColor="White"
AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All" />
<StackLayout AbsoluteLayout.LayoutBounds="0,0,1,1"
AbsoluteLayout.LayoutFlags="All">
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="70" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding Name}" Margin="0,3,0,0"
FontAttributes="Bold" FontSize="24" TextColor="White" />
<Label Grid.Row="1" Grid.Column="1" Text="{Binding StateShown}" FontSize="18" TextColor="White" />
<!-- <Image Source="power" Grid.RowSpan="2" Grid.Column="2" Margin="5" /> -->
<controls:IconView x:Name="btnPower" Source="power" Grid.RowSpan="2" Grid.Column="2" Margin="5"
Foreground="{Binding PowerStateColor}">
<controls:IconView.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.cmdTogglePower, Source={x:Reference MyListView}}" CommandParameter="{Binding Id}" />
</controls:IconView.GestureRecognizers>
</controls:IconView>
</Grid>
</StackLayout>
</AbsoluteLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Now what I expected to happen, was that when I click on the IconView I change the IconView color and the label with the StateShown binding. But nothing changes when I click on the IconView!
What am I doing wrong?
Add OnPropertyChanged method call to every property on DisplayedDevice for those you want the UI to notice.
public class DisplayedDevice : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged();
}
}
private int id;
public int Id
{
get
{
return id;
}
set
{
Id = value;
OnPropertyChanged();
}
}
private string state;
public string State
{
get
{
return state;
}
set
{
state = value;
OnPropertyChanged();
}
}
......
}
You can use MVVMHelpers NuGet Package and implement the ObservableObject Class from the NuGet directly to DisplayedDevice class and you need to make all the properties with a reference to the private variable.
public class DisplayedDevice: ObservableObject
{
string _textField = string.Empty;
public string TextField
{
get => _textField;
set => SetProperty(ref _textField, value);
}
bool _isBarChartVisible = false;
public bool IsBarChartVisible
{
get => _isBarChartVisible;
set => SetProperty(ref _isBarChartVisible, value);
}
}
}
Every public property should be backed by a private property of the same type. This is important so that any change in any property will be reflected on the UI using INotifyPropertyChanged

C# Xamarin forms Nested Listiews in TabbedPage with ObservableCollection

I am trying to create different tabs with different nested ListViews binding to ObservableCollection.
This is getting a bit over my head. It should be very simple to solve.
This is how my TabbedPage xaml looks like
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="scorePredict.Views.TabbedItemsPage" ItemsSource="{Binding Things}">
<!--Pages can be added as references or inline-->
<TabbedPage.ItemTemplate>
<DataTemplate>
<ContentPage Title="{Binding description}">
<StackLayout>
<ListView ItemsSource="{Binding DetailedViewModel.sortedItems}">
<ListView.HeaderTemplate >
<DataTemplate>
<Grid BackgroundColor="Black">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="Time" Grid.Column="0" Margin="5,0,0,0" TextColor="Gold" HorizontalOptions="StartAndExpand" HeightRequest="20" WidthRequest="50"/>
<Label Text="Host" Grid.Column="1" TextColor="Gold" HorizontalOptions="CenterAndExpand" Margin="0,0,170,0"/>
<Label Text="Guest" Grid.Column="1" TextColor="Gold" HorizontalOptions="CenterAndExpand" Margin="110,0,0,0"/>
<Label Text="TIP" HorizontalOptions="EndAndExpand" TextColor="Gold" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.Header >
<StackLayout Padding="10,5,0,5" BackgroundColor="Green" Orientation="Horizontal">
<Label Text="{Binding Name}" />
<Label Text="{Binding Description}" FontSize="Small"/>
</StackLayout>
</ListView.Header>
<ListView.GroupHeaderTemplate >
<DataTemplate>
<ViewCell Height="30">
<StackLayout Orientation="Horizontal"
BackgroundColor="Goldenrod"
VerticalOptions="FillAndExpand">
<Image Source="{Binding imagePath}" VerticalOptions="Center" HorizontalOptions="Start" HeightRequest="20" WidthRequest="20"/>
<Label Text="{Binding Intro} " VerticalOptions="Center"/>
<Label Text="{Binding Summary}" VerticalOptions="Center"/>
<Label Text="TIP" VerticalOptions="Center" HorizontalOptions="EndAndExpand"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Grid Padding="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Time}" Grid.Column="0"></Label>
<Label Text="{Binding TeamOne}" Grid.Column="1" HorizontalTextAlignment="End" HorizontalOptions="CenterAndExpand" Margin="0,0,170,0"></Label>
<Label Text="{Binding ScoreTeamOne}" Grid.Column="1" TextColor="White" HorizontalOptions="CenterAndExpand" HorizontalTextAlignment="Center" Margin="0,0,50,0" WidthRequest="20" BackgroundColor="Gray"></Label>
<Label Text="{Binding ScoreTeamTwo}" Grid.Column="1" TextColor="White" HorizontalOptions="CenterAndExpand" HorizontalTextAlignment="Center" Margin="0,0,5,0" WidthRequest="20" BackgroundColor="Gray"></Label>
<Label Text="{Binding TeamTwo}" Grid.Column="1" HorizontalTextAlignment="Start" HorizontalOptions="CenterAndExpand" Margin="110,0,0,0"></Label>
<Label Text="{Binding Tip}" Grid.Column="1" HorizontalOptions="EndAndExpand" WidthRequest="20" HorizontalTextAlignment="Center" TextColor="White" BackgroundColor="{Binding TipBGColor}"></Label>
</Grid>
</ViewCell.View>
</ViewCell>
<!--<TextCell Text="{Binding Name}" Detail="{Binding Description}"></TextCell>-->
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
</DataTemplate>
</TabbedPage.ItemTemplate>
</TabbedPage>
This is the background code
public TabbedItemsPage()
{
InitializeComponent();
BindingContext = new CurrentStatusDeviceViewModel();
}
public class CurrentStatusDeviceViewModel
{
public ObservableCollection<TabViewModel> Things { set; get; }
public CurrentStatusDeviceViewModel()
{
// Here I create three demo pages
Things = new ObservableCollection<TabViewModel>();
for (int i = 0; i < 7; i++)
{
Things.Add(new TabViewModel(i.ToString()) { description = "description" + i });
}
}
}
public class TabViewModel
{
public string description { set; get; }
public string TabID { set; get; }
public CurrentStatusDeviceDetailedViewModel DetailedViewModel { set; get; }
public TabViewModel(string tabID)
{
TabID = tabID;
// Pass Tab ID to the second view model
DetailedViewModel = new CurrentStatusDeviceDetailedViewModel(tabID);
}
}
public class CurrentStatusDeviceDetailedViewModel
{
public string CurrentID { set; get; }
public ObservableCollection<Titles> sortedItems { get; set; }
public ObservableCollection<Item> items { get; set; }
public CurrentStatusDeviceDetailedViewModel(string tabId)
{
CurrentID = tabId;
// I simulate the lists here
sortedItems = new ObservableCollection<Titles>();
items = new ObservableCollection<Item>();
for (int i = 0; i < 5; i++)
{
for (int x = 0; x < 10; x++)
{
items.Add(new Item { Time = CurrentID + i, TeamOne = "Barcelona", ScoreTeamOne = "2", TeamTwo = "Real Madrid", ScoreTeamTwo = "1" });
}
sortedItems.Add(new Titles(items.ToList()) { Intro = "This is a big header", Summary = "This is the end" });
items.Clear();
}
}
}
And these are my classes for the lists
public class Titles : ObservableCollection<Item>
{
//public List<Matches> Monkeys { get; set; }
public string Intro { get; set; }
public string Summary { get; set; }
public Uri imagePath { get; set; }
public Titles(List<Item> list) : base(list)
{
}
}
public class Item
{
public string Id { get; set; }
public string Text { get; set; }
public string Description { get; set; }
public string Href { get; set; }
public string Time { get; set; }
public string TeamOne { get; set; }
public string ScoreTeamOne { get; set; }
public string ScoreTeamTwo { get; set; }
public string TeamTwo { get; set; }
public string Tip { get; set; }
public Color TipBGColor { get; set; }
}
My problem here is that the five headers are created but items are not created within them and the titles of the headers are not binded.
It is just empty.
Any ideas?
What you need to do is set the IsGroupingEnabled of ListView to true :
<ListView ItemsSource="{Binding DetailedViewModel.sortedItems}" IsGroupingEnabled="True">
The document is here: customizing-list-appearance#grouping
You need to enable grouping in your Listview
<ListView ItemsSource="{Binding DetailedViewModel.sortedItems}" IsGroupingEnabled="True">

Max rows listview xamarin

I have a list view that displays a ladder of points on a sports team and am trying to only show the top 5 players. Is there anyway that this can be in XAML?
Thanks,
Ryan
EDIT: Here is my code
XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MIApp.PlayerPage">
<ContentPage.Content>
<StackLayout Margin="20,0,0,0" Orientation="Vertical" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
<Label Text="Player Info" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>
<Grid MinimumHeightRequest="200" RowSpacing="10" Padding="0">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Text="Goal Leaders" Grid.Row="0" Margin="0"/>
<ListView x:Name="GoalListView" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Margin="0,0,0,0" Spacing="0" VerticalOptions="Center">
<Label Text="{Binding StrFullName}" HorizontalOptions="Start" VerticalTextAlignment="Center"/>
<Label Text="{Binding IntGoals}" HorizontalOptions="End" VerticalTextAlignment="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label Text="Point Leaders" Grid.Row="2" Margin="0"/>
<ListView x:Name="PointListView" HasUnevenRows="true" Grid.Row="3" Margin="0,0,0,0">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Margin="0,0,0,0" Spacing="0" VerticalOptions="Center">
<Label Text="{Binding StrFullName}" HorizontalOptions="Start" VerticalTextAlignment="Center"/>
<Label Text="{Binding IntPoints}" HorizontalOptions="End" VerticalTextAlignment="Center"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Code Behind:
public partial class PlayerPage : ContentPage
{
public PlayerPage()
{
InitializeComponent();
}
protected async override void OnAppearing()
{
base.OnAppearing();
HttpClient client = new HttpClient();
string urlGoals = "https://melbourneicewebapi.azurewebsites.net/api/Player_Info/GetPlayer_Info?playerInfo=goals";
string urlPoints = "https://melbourneicewebapi.azurewebsites.net/api/Player_Info/GetPlayer_Info?playerInfo=points";
var responseGoals = await client.GetAsync(urlGoals);
var responsePoints = await client.GetAsync(urlPoints);
if (responsePoints.IsSuccessStatusCode)
{
string resGoals = "";
using (HttpContent contentGoals = responseGoals.Content)
{
Task<string> resultGoals = contentGoals.ReadAsStringAsync();
resGoals = resultGoals.Result;
var GoalsList = Players.PlayersItems.FromJson(resGoals);
GoalListView.ItemsSource = GoalsList;
}
string resPoints = "";
using (HttpContent contentPoints = responsePoints.Content)
{
Task<string> resultPoints = contentPoints.ReadAsStringAsync();
resPoints = resultPoints.Result;
var PointsList = Players.PlayersItems.FromJson(resPoints);
PointListView.ItemsSource = PointsList;
}
}
else
{
await DisplayAlert("Connection Error", "Please Connect to the internet and try again", "Ok");
}
}
}
Players Class where objects are created from JSON string and added into a list:
public class Players
{
public partial class PlayersItems
{
[JsonProperty("$id")]
public long Id { get; set; }
[JsonProperty("intPlayerID")]
public int IntPlayerId { get; set; }
[JsonProperty("strFirstName")]
public string StrFirstName { get; set; }
[JsonProperty("strSurname")]
public string StrSurname { get; set; }
[JsonProperty("intGamesPlayed")]
public int IntGamesPlayed { get; set; }
[JsonProperty("strPosition")]
public string StrPosition { get; set; }
[JsonProperty("intPlayerNumber")]
public int IntPlayerNumber { get; set; }
[JsonProperty("intGoals")]
public int IntGoals { get; set; }
[JsonProperty("intAssists")]
public int IntAssists { get; set; }
[JsonProperty("intPoints")]
public int IntPoints { get; set; }
public string StrFullName {
get
{
return StrFirstName.Trim() + " " + StrSurname.Trim();
}
}
}
public partial class PlayersItems
{
public static List<PlayersItems> FromJson(string json)
{
return JsonConvert.DeserializeObject<List<PlayersItems>>(json);
}
}
}
So what I am essentially doing is accessing an API that gives me two JSON Strings of all the data entries in the Players Table,One that is sorted by highest points, the other by highest goals, it is then converted into a list of objects from the Players Class, and then set to the appropriate List View.
use LINQ to select the top X scores before assigning them to ItemsSource. I'm not aware of any way to filter the data directly in XAML
var PointsList = Players.PlayersItems.FromJson(resPoints);
var topPoints = PointsList.OrderByDescending(x => x.PointsInt).Take(5).ToList();
PointListView.ItemsSource = topPoints;

Data changes reflected wrong in parent window

I have the following requirements:
Show TreeView of items
Show Details of selected item in TreeView.
A dialog to edit the selected item.
I've implemented these requirements but the third doesn't do what it is supposed to do so I'm stuck.
What I want it to do :
The edit dialog should be able to edit an item. This isn't a TreeViewItem but an instance of one of my classes.
Save the edits - A button that will just close the dialog.
Discard the edits - A button to reset the fields changed in the item and close dialog.
The second requirement does not work. If I edit a field and hit Cancel, the item details panel still shows the edits. I have debugged but I find that the underlying item is unchanged - however the item is displayed with the changed values.
Code:
Item class (Category)
public class Category
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Unit Unit { get; set; }
public virtual List<Category> ChildCategories { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual bool IsMainCategory { get; set; }
public Category()
{
ChildCategories = new List<Category>();
}
public virtual void AddChild(Category child)
{
ChildCategories.Add(child);
child.ParentCategory = this;
}
}
Item (Category) Details are shown in a label:
<DataTemplate DataType="{x:Type local:Category}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" SharedSizeGroup="a" />
<ColumnDefinition Width="6*" SharedSizeGroup="b" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<TextBlock Text="Name" Grid.Column="0" Grid.Row="0" Padding="5"/>
<TextBlock Text="{Binding Path=Name}" Grid.Column="1" Grid.Row="0" Padding="5"/>
<TextBlock Text="Description" Grid.Column="0" Grid.Row="1" Padding="5"/>
<TextBlock Text="{Binding Path=Description}" Grid.Column="1" Grid.Row="1" Padding="5"/>
</Grid>
</DataTemplate>
Event handler for Edit item in Main Window :
private void EditCategory(object sender, RoutedEventArgs e)
{
Category ctg = _tree.SelectedItem as Category;
if (ctg != null)
{
CategoryDefineWindow cdw = new CategoryDefineWindow();
cdw.CategoryObject = ctg;
cdw.ShowDialog();
}
}
Item editor window xaml:
<Window x:Class="BSRCat.View.CategoryDefineWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="150" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" SharedSizeGroup="a" />
<ColumnDefinition Width="7*" SharedSizeGroup="b" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<TextBlock Text="Name" Grid.Column="0" Grid.Row="0" Padding="5"/>
<TextBox Text="{Binding Path=CategoryObject.Name, RelativeSource={RelativeSource AncestorType=Window}}" Grid.Column="1" Grid.Row="0" Padding="5"/>
<TextBlock Text="Description" Grid.Column="0" Grid.Row="1" Padding="5"/>
<TextBox Text="{Binding Path=CategoryObject.Description, RelativeSource={RelativeSource AncestorType=Window}}" Grid.Column="1" Grid.Row="1" Padding="5"/>
<StackPanel Orientation="Horizontal" Grid.Row="2" Grid.ColumnSpan="2" HorizontalAlignment="Right">
<Button Content="Ok" Margin="5" Height="20" Width="30" Click="Confirmed"/>
<Button Content="Cancel" Margin="5" Height="20" Width="50" Click="Cancelled"/>
</StackPanel>
</Grid>
</Window>
Item editor window code behind :
public partial class CategoryDefineWindow : Window
{
public Category CategoryObject
{
get
{
return _category;
}
set
{
_category = value;
_initial = new Category() { Name = value.Name, Description = value.Description };
}
}
private Category _category;
private Category _initial;
public CategoryDefineWindow()
{
InitializeComponent();
}
private void Confirmed(object sender, RoutedEventArgs e)
{
Close();
}
private void Cancelled(object sender, RoutedEventArgs e)
{
_category.Name = _initial.Name;
_category.Description = _initial.Description;
Close();
}
}
I've debugged the CategoryDefineWindow.Cancelled method and the _category object is reset correctly. I can't find where it goes wrong.
Category class should be implemented INotifyPropertyChanged interface. WPF will be notified once the value of properties have changed.
public class Category : INotifyPropertyChanged
{
private string _Name;
private string _Description;
public virtual int Id { get; set; }
public virtual string Name
{
get
{
return _Name;
}
set
{
if (_Name == value)
return;
_Name = value;
NotifyPropertyChanged("Name");
}
}
public virtual string Description
{
get
{
return _Description;
}
set
{
if (_Description == value)
return;
_Description = value;
NotifyPropertyChanged("Description");
}
}
public virtual Unit Unit { get; set; }
public virtual List<Category> ChildCategories { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual bool IsMainCategory { get; set; }
public Category()
{
ChildCategories = new List<Category>();
}
public virtual void AddChild(Category child)
{
ChildCategories.Add(child);
child.ParentCategory = this;
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

Categories