WPF understanding Selector.IsSynchronizedWithCurrentItem - c#

Do not know if this is specific to the Infragistics xamDataGrid but here goes the question:
Infragistics xamDataGrid exposes a property IsSynchronizedWithCurrentItem, which according to their documentation, synchronizes ActiveRecord with current item of a datasource that implements ICollectionView.
I have the following MasterDetails window with details (ContentControl) content based on the type of objects bound to the grid:
<DockPanel Name="dockPanel" LastChildFill="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="5" MaxHeight="5"/>
<RowDefinition/>
</Grid.RowDefinitions>
<igDP:XamDataGrid
Name="dataGrid"
IsSynchronizedWithCurrentItem="True"
SelectedItemsChanged="dataGrid_SelectedItemsChanged">
</igDP:XamDataGrid>
<GridSplitter
Style="{StaticResource blueHrizontalGridSplitter}"
Grid.Row="1" Grid.ColumnSpan="2"
BorderThickness="1" Margin="1,0"
HorizontalAlignment="Stretch" />
<ContentControl Grid.Row="2" Name="contentControl" />
</Grid>
</DockPanel>
In code behind, I am attempting to establish a link between the current item of the grid's data source to the DataContext of the details control in my MasterDetailsWindow's constructor as follows:
if (detailsControl != null)
{
var fwDControl = detailsControl as FrameworkElement;
if (fwDControl != null)
{
var b = new Binding() { ElementName = "dataGrid", Path = new PropertyPath("DataSource") };
fwDControl.SetBinding(DataContextProperty, b);
}
contentControl.Content = detailsControl;
}
else
{
var b = new Binding() { ElementName = "dataGrid", Path = new PropertyPath("DataSource") };
contentControl.SetBinding(ContentProperty, b);
b = new Binding("DataDetailsTemplate");
contentControl.SetBinding(ContentTemplateProperty, b);
}
When constructing a instance of the MasterDetails, the caller needs to provide either a detailsControl object or a string representing the URL to DataTemplate. If a detailsControl is provided, I execute code that checks if details is not null. Otherwise, I assume DataDetailsTemplate is provided instead.
I would have doubted my thinking here but if I construct an instance of the MasterDetails window, with a URL that resolves to the following dataTemplate:
<DataTemplate x:Key="LogDetailsTemplate">
<Grid Margin="5,5,5,0">
<TextBox Text="{Binding Message}" TextWrapping="WrapWithOverflow"/>
</Grid>
</DataTemplate>
selecting an item in the grid, displays the selected object's corresponding Message property in the TextBox.
However, if I provide a custom detailsControl object that derives from UserControl, selecting an item in the grid, does not cause change the DataContext of my detailsControl. Why is this?
TIA.

Whoa there!!!!! I may be wrong but it looks like you've come from a WinForms background and are trying to to do things in WPF the way you would for WinForms.
The good news is, you don't have to: Master detail can be handled using a simple forwardslash. In the example below, look at the bindings in MainWindow.xaml - the forwardslash indicates the currently selected item.
MODELS
public class Country
{
public string Name { get; set; }
public int Population { get; set; }
}
public class Continent
{
public string Name { get; set; }
public int Area { get; set; }
public IList<Country> Countries { get; set; }
}
VIEWMODELS
public class MainViewModel
{
private ObservableCollection<ContinentViewModel> _continents;
public ObservableCollection<ContinentViewModel> Continents
{
get { return _continents; }
set
{
_continents = value;
ContinentView = new ListCollectionView(_continents);
ContinentView.CurrentChanged += (sender, agrs) => CurrentContinent = ContinentView.CurrentItem as ContinentViewModel;
}
}
public ListCollectionView ContinentView {get; private set;}
/// <summary>
/// Use this to determine the current item in the list
/// if not willing to use \ notation in the binding.
/// </summary>
public ContinentViewModel CurrentContinent { get; set; }
}
public class ContinentViewModel
{
private Continent _model;
public Continent Model
{
get { return _model; }
set
{
_model = value;
Countries = _model.Countries
.Select(p => new CountryViewModel { Model = p })
.ToList();
}
}
public string Name
{
get { return Model.Name; }
}
public int Area
{
get { return Model.Area; }
}
public List<CountryViewModel> Countries { get; private set; }
}
public class CountryViewModel
{
public Country Model { get; set; }
public string Name
{
get { return Model.Name; }
}
public int Population
{
get { return Model.Population; }
}
}
MainWindow.xaml
<Window x:Class="XamDataGridMasterDetail.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Views="clr-namespace:XamDataGridMasterDetail.Views"
xmlns:igDP="http://infragistics.com/DataPresenter"
Title="MainWindow">
<Grid>
<StackPanel Orientation="Vertical">
<!-- Continent list -->
<igDP:XamDataGrid HorizontalAlignment="Left"
Margin="10,10,0,0"
Name="xamDataGrid1"
Height="300"
VerticalAlignment="Top"
DataSource="{Binding ContinentView}"
IsSynchronizedWithCurrentItem="True">
<igDP:XamDataGrid.FieldSettings>
<igDP:FieldSettings CellClickAction="SelectRecord" />
</igDP:XamDataGrid.FieldSettings>
<igDP:XamDataGrid.FieldLayouts>
<igDP:FieldLayout>
<igDP:FieldLayout.Settings>
<igDP:FieldLayoutSettings AutoGenerateFields="False" />
</igDP:FieldLayout.Settings>
<igDP:FieldLayout.Fields>
<igDP:Field Name="Name"
Label="Name" />
<igDP:Field Name="Area"
Label="Area" />
<igDP:UnboundField Label="# Countries"
Binding="{Binding Countries.Count}" />
</igDP:FieldLayout.Fields>
</igDP:FieldLayout>
</igDP:XamDataGrid.FieldLayouts>
</igDP:XamDataGrid>
<!-- Continent detail -->
<ListBox ItemsSource="{Binding ContinentView/Countries}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True"
Height="200" />
<!-- Country detail -->
<StackPanel Orientation="Horizontal">
<Label Content="Name: " />
<TextBlock Text="{Binding ContinentView/Countries/Name}" />
<Label Content="Population: " />
<TextBlock Text="{Binding ContinentView/Countries/Population}" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
App.xaml.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
using XamDataGridMasterDetail.ViewModels;
using System.Collections.ObjectModel;
using XamDataGridMasterDetail.Model;
namespace XamDataGridMasterDetail
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
base.OnSessionEnding(e);
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var view = new MainWindow();
var vm = new MainViewModel();
vm.Continents = new ObservableCollection<ContinentViewModel>();
vm.Continents.Add(new ContinentViewModel
{
Model = new Continent
{
Name = "Australasia",
Area = 100000,
Countries = new[]
{
new Country
{
Name="Australia",
Population=100
},
new Country
{
Name="New Zealand",
Population=200
}
}
}
});
vm.Continents.Add(new ContinentViewModel
{
Model = new Continent
{
Name = "Europe",
Area = 1000000,
Countries = new[]
{
new Country
{
Name="UK",
Population=70000000
},
new Country
{
Name="France",
Population=50000000
},
new Country
{
Name="Germany",
Population=75000000
}
}
}
});
view.DataContext = vm;
view.Show();
}
}
}

Related

Binding to an enum inside a child view model combobox object is not renders on UI

I have a Parent ViewModel which contains a child view model object inside with some enum,
When I open the UI I see that the enum value not renders as expected from the RaisePropertyChanged event on first time loading, however, after setting a value from the UI the value changes and renders as expected.
Parent VM xaml
<UserControl x:Class="RemoteDebugger.App.Views.ConfigurationControl"
d:DataContext="{d:DesignInstance viewModels:ConfigurationViewModel}">
<Grid>
//Some properties...
<local:ProjectEditor Grid.Row="5" Grid.Column="1" BorderBrush="#FFCFCBCB" BorderThickness="0,1,0,1" Grid.ColumnSpan="3" DataContext="{Binding MainProject,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ></local:ProjectEditor>
</Grid>
Parent VM cs
using RemoteDebugger.App.Utils;
using System.Collections.Generic;
using System.IO;
using System.Windows.Input;
using Microsoft.Win32;
using RemoteDebugger.Model;
using RemoteDebugger.Model.DataStructures;
namespace RemoteDebugger.App.ViewModels
{
public class ConfigurationViewModel : BaseViewModel
{
private ICommand _saveConfiguration;
private ICommand _loadConfiguration;
private Configuration _configuration;
private ProjectViewModel _mainProject;
private ExtraProjectsViewModel _extraProjects;
public ConfigurationViewModel()
{
_configuration = RemoteDebuggerManager.Instance.Configuration;
_mainProject = new ProjectViewModel(_configuration.MainProject);
_extraProjects = new ExtraProjectsViewModel(_configuration.ExtraProjectsToCopy);
}
public ICommand SaveConfiguration
{
get
{
return _saveConfiguration ??= new Command(o =>
{
_configuration.MainProject = _mainProject.Project;
_configuration.ExtraProjectsToCopy = _extraProjects.Projects;
RemoteDebuggerManager.Instance.SaveConfigurations();
});
}
}
public ICommand LoadConfiguration
{
get
{
return _loadConfiguration ??= new Command(o =>
{
var fd = new OpenFileDialog
{
Multiselect = false,
Filter = "XML Files (*.xml)|*.xml",
InitialDirectory = ConfigurationHandler.ConfigurationFolder
};
var path = fd.ShowDialog();
if (path == null || !File.Exists(fd.FileName))
return;
_configuration = RemoteDebuggerManager.Instance.LoadConfigurations(fd.FileName);
UpdateView();
});
}
}
private void UpdateView()
{
OsBits = _configuration.OsBits;
VisualStudioVersion = _configuration.VisualStudioVersion;
CopyExtraPaths = _configuration.CopyExtraPaths;
Ip = _configuration.ControllerIp;
_mainProject.Project = _configuration.MainProject;
_extraProjects.Projects = _configuration.ExtraProjectsToCopy;
}
public string VisualStudioVersion
{
get => _configuration.VisualStudioVersion;
set
{
_configuration.VisualStudioVersion = value;
RaisePropertyChanged(nameof(VisualStudioVersion));
}
}
public string OsBits
{
get => _configuration.OsBits;
set
{
_configuration.OsBits = value;
RaisePropertyChanged(nameof(OsBits));
}
}
public string Ip
{
get => _configuration.ControllerIp;
set
{
_configuration.ControllerIp = value;
RaisePropertyChanged(nameof(Ip));
}
}
public string WaitForDebugFileDestination
{
get => _configuration.WaitForDebugFileDestination;
set
{
_configuration.WaitForDebugFileDestination = value;
RaisePropertyChanged(nameof(WaitForDebugFileDestination));
}
}
public bool CopyExtraPaths
{
get => _configuration.CopyExtraPaths;
set
{
_configuration.CopyExtraPaths = value;
RaisePropertyChanged(nameof(CopyExtraPaths));
}
}
public ProjectViewModel MainProject
{
get => _mainProject;
set
{
_mainProject = value;
RaisePropertyChanged(nameof(MainProject));
}
}
public ExtraProjectsViewModel ExtraProjects
{
get => _extraProjects;
set
{
_extraProjects = value;
RaisePropertyChanged(nameof(ExtraProjects));
}
}
public List<string> VisualStudioSupportedVersions => EnvironmentValues.VisualStudioVersions;
public List<string> OsBitsTypes => EnvironmentValues.OsBits;
}
}
Child VM xaml
<UserControl x:Class="RemoteDebugger.App.Views.ProjectEditor"
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:viewModels="clr-namespace:RemoteDebugger.App.ViewModels"
xmlns:local="clr-namespace:RemoteDebugger.App.Views"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModels:ProjectViewModel}"
>
<Grid>
<Grid.RowDefinitions>
...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
<ComboBox Grid.Row ="1" Grid.Column="1" ItemsSource="{Binding ProjectTypes}" VerticalAlignment="Center" SelectedItem="{Binding ProjectType,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" FontSize="14" />
<TextBlock Grid.Row ="1" Grid.Column="0" Text="Project type" TextWrapping="Wrap"/>
<TextBlock Grid.Row ="5" Grid.Column="0" Text="Local project path" VerticalAlignment="Center" TextWrapping="Wrap"/>
<TextBox Grid.Row ="5" Grid.Column="1" Height="30" Text="{Binding LocalDllDirRoot,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row ="7" Grid.Column="0" Text="Remote project path" VerticalAlignment="Center" TextWrapping="Wrap"/>
<TextBox Grid.Row ="7" Grid.Column="1" Height="30" Text="{Binding RemoteDllDirRoot,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row ="9" Grid.Column="0" Text="Arguments" VerticalAlignment="Center" TextWrapping="Wrap"/>
<TextBox Grid.Row ="9" Grid.Column="1" Height="30" Text="{Binding Arguments,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row ="3" Grid.Column="0" Text="Executable name" VerticalAlignment="Center" TextWrapping="Wrap"/>
<TextBox Grid.Row ="3" Grid.Column="1" Height="30" Text="{Binding ExecutableName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</Grid>
Child VM cs file
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using IntelGenericUIFramework.Common;
using RemoteDebugger.Model.DataStructures;
namespace RemoteDebugger.App.ViewModels
{
public class ProjectViewModel : BaseViewModel,ICloneable
{
private ProjectDescriptor _project;
public ProjectViewModel()
{
_project = new ProjectDescriptor();
}
public ProjectViewModel(ProjectDescriptor projectDescriptor)
{
_project = projectDescriptor == null ? new ProjectDescriptor() :
(ProjectDescriptor)projectDescriptor.Clone() ;
UpdateProperties();
}
private void UpdateProperties()
{
foreach (var prop in this.GetType().GetProperties())
{
RaisePropertyChanged(nameof(prop.Name));
}
}
#region Main project values
public string LocalDllDirRoot
{
get => _project.LocalDllDirRoot;
set
{
Project.LocalDllDirRoot = value;
RaisePropertyChanged(nameof(LocalDllDirRoot));
RaisePropertyChanged(nameof(Project));
}
}
public string RemoteDllDirRoot
{
get => _project.RemoteDllDirRoot;
set
{
_project.RemoteDllDirRoot = value;
RaisePropertyChanged(nameof(RemoteDllDirRoot));
RaisePropertyChanged(nameof(Project));
}
}
public string ExecutableName
{
get => Project.ExecutableName;
set
{
_project.ExecutableName = value;
RaisePropertyChanged(nameof(ExecutableName));
RaisePropertyChanged(nameof(Project));
}
}
public string Arguments
{
get => Project.Arguments;
set
{
_project.Arguments = value;
RaisePropertyChanged(nameof(Arguments));
RaisePropertyChanged(nameof(Project));
}
}
public bool IsService
{
get => Project.IsService;
set
{
_project.IsService = value;
RaisePropertyChanged(nameof(IsService));
RaisePropertyChanged(nameof(Project));
}
}
public ProjectType ProjectType
{
get => Project.ProjectType;
set
{
_project.ProjectType = value;
RaisePropertyChanged(nameof(ProjectType));
RaisePropertyChanged(nameof(Project));
}
}
#endregion
public void Clear()
{
LocalDllDirRoot = string.Empty;
RemoteDllDirRoot = string.Empty;
ExecutableName = string.Empty;
IsService = false;
Arguments = string.Empty;
ProjectType = ProjectType.None;
}
public ProjectDescriptor Project
{
get => _project;
set
{
if (value == null)
return;
_project = (ProjectDescriptor)value.Clone();
UpdateProperties();
}
}
public object Clone()
{
return Project.Clone();
}
public List<string> ProjectTypes => Enum.GetNames(typeof(ProjectType)).ToList();
}
}
Example of the broken binding
the marked field not loading the correct selected enum value and show a blank selection, while the other properties are shown as expected.
The problem is that 'ProjectType' property in Child VM cs file
not renders on startup in UI in Parent VM xaml
Thanks ahead!
ProjectTypes is a List<string> and ProjectType is a ProjectType.
The types should match so either change the type of the source collection:
public List<ProjectType> ProjectTypes =>
Enum.GetValues(typeof(ProjectType)).OfType<ProjectType>().ToList();
...or the type of the SelectedItem source property to string.

My ListView is showing the same item twice. How do I fix it?

I have a ComboBox that allows the user to select a category and a ListView that is bound to an ObservableCollection of items in the selected category. When the user selects a different category, the items in the collection are updated. Sometimes this works as expected, but sometimes the list of items is mangled. It shows a duplicate item when there should be two separate items.
The results seem to depend on which category I'm switching from. For example, if I switch from a category with no items to a category with two items, the same item is shown twice. But if I switch from a category with four items to that same category with two items, they are shown correctly.
Here is a repro:
MainPage.xaml
<Page
x:Class="ListViewDuplicateItem_Binding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ListViewDuplicateItem_Binding">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
Grid.Column="0"
ItemsSource="{Binding ViewModel.Groups}"
SelectedItem="{Binding ViewModel.SelectedGroup, Mode=TwoWay}" />
<ListView
Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding ViewModel.Widgets}"
SelectedItem="{Binding ViewModel.SelectedWidget, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Widget">
<TextBlock Text="{Binding Id}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<local:MyControl
Grid.Row="1"
Grid.Column="1"
Text="{Binding ViewModel.SelectedWidget.Id, Mode=OneWay}" />
</Grid>
</Page>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace ListViewDuplicateItem_Binding
{
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
DataContext = this;
}
public MainViewModel ViewModel { get; } = new MainViewModel();
}
}
MainViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace ListViewDuplicateItem_Binding
{
public class MainViewModel : INotifyPropertyChanged
{
private string _selectedGroup;
private Widget _selectedWidget;
public MainViewModel()
{
PropertyChanged += HomeViewModel_PropertyChanged;
SelectedGroup = Groups.First();
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Groups { get; } = new ObservableCollection<string>(DataSource.AllGroups);
public string SelectedGroup
{
get => _selectedGroup;
set
{
if (_selectedGroup != value)
{
_selectedGroup = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedGroup)));
}
}
}
public Widget SelectedWidget
{
get => _selectedWidget;
set
{
if (_selectedWidget != value)
{
_selectedWidget = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedWidget)));
}
}
}
public ObservableCollection<Widget> Widgets { get; } = new ObservableCollection<Widget>();
private void HomeViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectedGroup))
{
var widgetsToLoad = DataSource.GetWidgetsForGroup(SelectedGroup);
// Add widgets in this group
widgetsToLoad.Except(Widgets).ToList().ForEach(w => Widgets.Add(w));
// Remove widgets not in this group
Widgets.Except(widgetsToLoad).ToList().ForEach(w => Widgets.Remove(w));
// Select the first widget
if (SelectedWidget == null && Widgets.Any())
{
SelectedWidget = Widgets.First();
}
}
}
}
}
DataSource.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace ListViewDuplicateItem_Binding
{
public static class DataSource
{
public static ObservableCollection<string> AllGroups { get; } = new ObservableCollection<string>
{
"First Widget",
"First Two Widgets",
"Last Two Widgets",
"All Widgets",
"None"
};
public static List<Widget> AllWidgets { get; } = new List<Widget>
{
new Widget()
{
Id = 1,
},
new Widget()
{
Id = 2,
},
new Widget()
{
Id = 3,
},
new Widget()
{
Id = 4,
}
};
public static List<Widget> GetWidgetsForGroup(string group)
{
switch (group)
{
case "First Widget":
return new List<Widget> { AllWidgets[0] };
case "First Two Widgets":
return new List<Widget> { AllWidgets[0], AllWidgets[1] };
case "Last Two Widgets":
return new List<Widget> { AllWidgets[2], AllWidgets[3] };
case "All Widgets":
return new List<Widget>(AllWidgets);
default:
return new List<Widget>();
}
}
}
}
Widget.cs
namespace ListViewDuplicateItem_Binding
{
public class Widget
{
public int Id { get; set; }
}
}
MyControl.xaml
<UserControl
x:Class="ListViewDuplicateItem_Binding.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox Text="{x:Bind Text, Mode=TwoWay}" />
</UserControl>
MyControl.xaml.cs
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ListViewDuplicateItem_Binding
{
public sealed partial class MyControl : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(MyControl), new PropertyMetadata(null));
public MyControl()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
}
This only seems to occur when the project includes a custom control that uses the Binding markup.
In the example above, if MyControl is removed from MainPage.xaml, it works as expected.
Likewise, if <local:MyControl Text="{Binding ViewModel.SelectedWidget.Id}" /> is changed to <local:MyControl Text="{x:Bind ViewModel.SelectedWidget.Id}" />, the example works as expected
This appears to be a bug in the ListView control, but you can work around it by using {x:Bind} compiled bindings.
Edit: Upon further investigation, the custom control may have been a red herring. Changing the custom control to a standard TextBox does not resolve the issue as I previously thought. The issue can be reproduced without the custom control. Nonetheless, using {x:Bind} or removing the control entirely does resolve the issue in this case.
Updating my project to use {x:Bind} (compiled bindings) appeared to resolve the issue, but a week later I unexpectedly started seeing duplicate items in my ListView again. This time I discovered three other factors that contributed to this issue.
I added a FallbackValue to the TextBoxes bound to the SelectedItem so they would be cleared when no item was selected. If I remove the FallbackValue, the list items are not duplicated. However, I need this setting.
I discovered that the order in which I add and remove items with the ObservableCollection bound to the ListView is important. If I add new items first and then remove old items, list items are duplicated. If I remove old items first and then add new items, the items are not duplicated. However, I'm using AutoMapper.Collection to update this collection, so I have no control over the order.
A colleague suggested that this bug may be related to the ListView.SelectedItem. I discovered that if I set the selected item to null before removing it from the collection, list items are not duplicated. This is the solution I am now using.
Here's an example:
// This resolves the issue:
if (!widgetsToLoad.Contains(SelectedWidget))
{
SelectedWidget = null;
}
// AutoMapper.Collection updates collections in this order. The issue does not occur
// if the order of these two lines of code is reversed.
{
// Add widgets in this group
widgetsToLoad.Except(Widgets).ToList().ForEach(w => Widgets.Add(w));
// Remove widgets not in this group
Widgets.Except(widgetsToLoad).ToList().ForEach(w => Widgets.Remove(w));
}
For a full repro, replace the code blocks in the question with these changes:
MainPage.xaml
<Page
x:Class="ListViewDuplicateItem_Fallback.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ListViewDuplicateItem_Fallback">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.Groups}"
SelectedItem="{x:Bind ViewModel.SelectedGroup, Mode=TwoWay}" />
<ListView
Grid.Row="1"
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.Widgets}"
SelectedItem="{x:Bind ViewModel.SelectedWidget, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Widget">
<TextBlock Text="{x:Bind Id}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBox
Grid.Row="1"
Grid.Column="1"
Text="{x:Bind ViewModel.SelectedWidget.Id, Mode=OneWay, FallbackValue=''}" />
</Grid>
</Page>
MainViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
namespace ListViewDuplicateItem_Fallback
{
public class MainViewModel : INotifyPropertyChanged
{
private string _selectedGroup;
private Widget _selectedWidget;
public MainViewModel()
{
PropertyChanged += HomeViewModel_PropertyChanged;
SelectedGroup = Groups.First();
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Groups { get; } = new ObservableCollection<string>(DataSource.AllGroups);
public string SelectedGroup
{
get => _selectedGroup;
set
{
if (_selectedGroup != value)
{
_selectedGroup = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedGroup)));
}
}
}
public Widget SelectedWidget
{
get => _selectedWidget;
set
{
if (_selectedWidget != value)
{
_selectedWidget = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedWidget)));
}
}
}
public ObservableCollection<Widget> Widgets { get; } = new ObservableCollection<Widget>();
private void HomeViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectedGroup))
{
var widgetsToLoad = DataSource.GetWidgetsForGroup(SelectedGroup);
// This resolves the issue:
//if (!widgetsToLoad.Contains(SelectedWidget))
//{
// SelectedWidget = null;
//}
// AutoMapper.Collection updates collections in this order. The issue does not occur
// if the order of these two lines of code is reversed. I do not simply clear the
// collection and reload it because this clears the selected item even when it is in
// both groups, and the animation is much smoother if items are not removed and reloaded.
{
// Add widgets in this group
widgetsToLoad.Except(Widgets).ToList().ForEach(w => Widgets.Add(w));
// Remove widgets not in this group
Widgets.Except(widgetsToLoad).ToList().ForEach(w => Widgets.Remove(w));
}
// Select the first widget
if (SelectedWidget == null && Widgets.Any())
{
SelectedWidget = Widgets.First();
}
}
}
}
}
DataSource.cs
using System.Collections.Generic;
using System.Linq;
namespace ListViewDuplicateItem_Fallback
{
public static class DataSource
{
public static List<string> AllGroups { get; set; } = new List<string> { "Group 1", "Group 2", "Group 3" };
public static List<Widget> AllWidgets { get; set; } = new List<Widget>(Enumerable.Range(1, 11).Select(widgetId => new Widget { Id = widgetId }));
public static List<Widget> GetWidgetsForGroup(string group)
{
switch (group)
{
case "Group 1":
return AllWidgets.Take(4).ToList();
case "Group 2":
return AllWidgets.Skip(4).Take(4).ToList();
case "Group 3":
return AllWidgets.Take(1).Union(AllWidgets.Skip(8).Take(3)).ToList();
default:
return new List<Widget>();
}
}
}
}

How to update list view item after click?

I want to create a list view same list wifi of Window 10. When user click to a list item, it will show more information.
I don't know how to show that additional data in Listview Item of UWP app on click one item?
before selected Wifi node
after selected Wifi node
in order to show additional data you can use Grid or StackPanel or whatever suits your needs with visibility collapsed and when the user clicks the item it will be set to show. Here I demonstrated how you can do this with a simple ListView:
This is my MainPage:
<Page
x:Class="App1.MainPage"
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:local="using:App1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Name="DummyPage"
mc:Ignorable="d">
<Page.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView
Name="lvDummyData"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsItemClickEnabled="True"
ItemClick="lvDummyData_ItemClick"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding FirstName}" />
<StackPanel
Grid.Row="1"
Margin="0,20,0,0"
Visibility="{Binding ShowDetails, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<TextBlock Text="{Binding LastName}" />
<TextBlock Text="{Binding Adress}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
This is my code behind:
using System.Collections.ObjectModel;
using System.ComponentModel;
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace App1
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public ObservableCollection<DummyData> DummyData { get; set; }
private DummyData tempSelectedItem;
public MainPage()
{
DummyData = new ObservableCollection<DummyData>();
DummyData.Add(new DummyData()
{
Adress = "London",
FirstName = "Shella",
LastName = "Schranz",
ShowDetails = false
});
DummyData.Add(new DummyData()
{
Adress = "New York",
FirstName = "Karyl",
LastName = "Lotz",
ShowDetails = false
});
DummyData.Add(new DummyData()
{
Adress = "Pasadena",
FirstName = "Jefferson",
LastName = "Kaur",
ShowDetails = false
});
DummyData.Add(new DummyData()
{
Adress = "Berlin",
FirstName = "Soledad",
LastName = "Farina",
ShowDetails = false
});
DummyData.Add(new DummyData()
{
Adress = "Brazil",
FirstName = "Cortney",
LastName = "Mair",
ShowDetails = false
});
this.InitializeComponent();
lvDummyData.ItemsSource = DummyData;
}
private void lvDummyData_ItemClick(object sender, ItemClickEventArgs e)
{
DummyData selectedItem = e.ClickedItem as DummyData;
selectedItem.ShowDetails = true;
if (tempSelectedItem != null)
{
tempSelectedItem.ShowDetails = false;
selectedItem.ShowDetails = true;
}
tempSelectedItem = selectedItem;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChangeEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DummyData : INotifyPropertyChanged
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Adress { get; set; }
private bool showDetails;
public bool ShowDetails
{
get
{
return showDetails;
}
set
{
showDetails = value;
RaisePropertyChangeEvent(nameof(ShowDetails));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChangeEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In my code-behind I have a variable tempSelectedItem which holds the previous clicked item so that you can hide its information.
In order to display and hide the information accordingly we need a simple BoolToVisibilityConverter:
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace App1
{
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
bool boolValue = (bool)value;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
Hope this helps.

How to access View control's from the ViewModel?

I've come across the situation, where I need access to the controls in the View from the ViewModel. In order to code a method that adds the selected item from a ComboBox to a list.
My question is how do I access the View controls from the ViewModel? Is there a certain design pattern I should follow to allow this?
The method below is coded in the View's code behind, which I know is bad practice if following the MVVM pattern, due to the tight coupling involved. Which is why I'm aiming to move this method the the ViewModel.
The method's purpose is to take the currently selected items from two ComboBoxes and add to a Key/Value List:
public void AddGradeSubjectChoiceToList()
{
string SelectedSubjectName = "null data";
int SelectedPoints = 01;
SelectedSubjectName = subjectCmbBx.SelectedItem.ToString();
try {
SelectedPoints = int.Parse(ordinaryGradeCmbBx.SelectedValue.ToString());
}
catch (Exception e)
{
//log error here..
}
List<StringKeyValue> SubjectPointKVTemp = new List<StringKeyValue>();
//Add selected pair to list
SubjectPointKVTemp.Add(new StringKeyValue { Key = SelectedSubjectName, Value = SelectedPoints });
SubjectPointKV = SubjectPointKVTemp;
}
The XAML for the MainPage is set up like this, with two combo boxes, for subjects and grades. And the addGrade button which will call the method to add the selected pair to a list:
<Page x:Class="LC_Points.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:converter="using:LC_Points.Converter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="using:LC_Points"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
DataContext="{Binding Source={StaticResource Locator}}"
mc:Ignorable="d">
<Page.Resources>
<converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converter:BoolToNonVisibilityConverter x:Key="BoolToNonVisibilityConverter" />
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40*" />
<RowDefinition Height="20*" />
<RowDefinition Height="30*" />
<RowDefinition Height="30*" />
<RowDefinition Height="20*" />
<RowDefinition Height="20*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<ComboBox x:Name="subjectCmbBx"
Grid.Row="1"
Grid.ColumnSpan="2"
Width="174"
HorizontalAlignment="Left"
VerticalAlignment="Top"
DisplayMemberPath="Subject"
Header="Subjects"
ItemsSource="{Binding Subjects}"
PlaceholderText="Pick a subject" />
<ComboBox x:Name="ordinaryGradeCmbBx"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Width="170"
HorizontalAlignment="Right"
DisplayMemberPath="Key"
Header="Grades"
ItemsSource="{Binding OrdinaryGradePointKV}"
PlaceholderText="Pick a grade"
Visibility="{Binding IsHigherToggled,
Mode=TwoWay,
Converter={StaticResource BoolToNonVisibilityConverter}}" />
<Button x:Name="addGradeBtn"
Grid.Row="2"
HorizontalAlignment="Left"
Command="{Binding Path=AddGradeCommand}"
Content="Add Grade" />
<ToggleButton x:Name="ordinaryTglBtn"
Grid.Row="3"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Content="Ordinary"
IsChecked="{Binding IsOrdinaryToggled,
Mode=TwoWay}" />
</Grid>
</Page>
The stripped down implementation of the ViewModel is as follows for reference, showing in comments how I pan to implement the AddGradeSubjectChoiceToList() method:
namespace LC_Points.ViewModel
{
public class MainViewModel : ViewModelBase
{
private ScoreModel _scoreModel;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(ScoreModel GradeModel)
{
_scoreModel = GradeModel;
//call methods to initilise list data
GetSubjectTypes();
GetOrdinaryGradePairs();
}
public List<ScoreModel> Subjects { get; set; }
public List<StringKeyValue> OrdinaryGradePointKV { get; set; }
//ordinary toggle button bool
private bool _isOrdinaryToggled;
public bool IsOrdinaryToggled
{
get
{
return _isOrdinaryToggled;
}
set
{
_isOrdinaryToggled = value;
RaisePropertyChanged("IsOrdinaryToggled");
}
}
//Need to add same method from code behind to VM here
//but don't have access to the View's controlsMethod to store Subject and Grade from Combo Boxes
public void AddGradeSubjectChoiceToList()
{
}
//This Relay Command is tied to the button in the View, that will be used
//to call the AddGradeSubjectChoiceToList method
RelayCommand addGradeCommand;
public RelayCommand AddGradeCommand
{
get
{
if (addGradeCommand == null)
{
addGradeCommand = new RelayCommand(() =>
{
AddGradeSubjectChoiceToList
});
}
return addGradeCommand;
}
}
public class StringKeyValue
{
public string Key { get; set; }
public int Value { get; set; }
}
public void GetOrdinaryGradePairs()
{
List<StringKeyValue> ordinaryGradePointKVTemp = new List<StringKeyValue>();
ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "A1", Value = 60 });
ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "A2", Value = 50 });
ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "B1", Value = 45 });
ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "B2", Value = 40 });
ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "B3", Value = 35 });
ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "C1", Value = 30 });
ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "C2", Value = 25 });
ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "C3", Value = 20 });
OrdinaryGradePointKV = ordinaryGradePointKVTemp;
}
public void GetSubjectTypes()
{
List<ScoreModel> subjectList = new List<ScoreModel>();
// Adding Subjects to List
subjectList.Add(new ScoreModel { Subject = "Accounting" });
subjectList.Add(new ScoreModel { Subject = "Agricultural Economics" });
subjectList.Add(new ScoreModel { Subject = "Agricultural Science" });
subjectList.Add(new ScoreModel { Subject = "Ancient Greek" });
subjectList.Add(new ScoreModel { Subject = "Applied Math" });
subjectList.Add(new ScoreModel { Subject = "Arabic" });
subjectList.Add(new ScoreModel { Subject = "Art" });
subjectList.Add(new ScoreModel { Subject = "Artistic & Creative Group" });
subjectList.Add(new ScoreModel { Subject = "Biology" });
subjectList.Add(new ScoreModel { Subject = "Business" });
Subjects = subjectList;
}
}
}
In order to follow MVVM you should use generic types and bind them to the control.
Here's an example in one of my ViewModels:
private string[] _optionItems;
public string[] OptionItems
{
get
{
return _optionItems;
}
set
{
if (_optionItems == value)
return;
_optionItems = value;
OnPropertyChanged();
}
}
private string _selectedOption;
public string SelectedOption
{
get
{
return _selectedOption;
}
set
{
if (_selectedOption == value)
return;
_selectedOption = value;
OnPropertyChanged();
}
}
and here's the XAML code:
<ComboBox ItemsSource="{Binding OptionItems}" SelectedItem="{Binding SelectedOption}"/>
As the above comments point out, the solution of coupling the View to the ViewModel is bad practice when following the MVVM pattern.
The appropriate solution is to set up a property for the ComboBox's SelectedItem and bind the View to that, so that the Selected items are available for leveraging in the VM.
1. Set up binding to SelectedItem in the View:
SelectedItem="{Binding SelectedSubject,Mode=TwoWay}"
2. Create the ComboBox property in the ViewModel:
private ComboBox _selectedSubject;
public ComboBox SelectedSubject
{
get { return _selectedSubject; }
set
{
_selectedSubject = value;
RaisePropertyChanged("SelectedSubject");
}
}

What design pattern should I use to make such dialogs?

I want to develop dialog for editing objects that make use of polymorphism. Currently I'm using this pattern:
MyObject.cs:
using System;
namespace WpfApplication3
{
public class MyObject
{
public string Title { get; set; }
public MySettings Settings { get; set; }
}
public abstract class MySettings
{
public abstract string GetSettingsString();
}
public class MyBoolSettings : MySettings
{
public bool BoolSetting { get; set; }
public override string GetSettingsString()
{
return "BoolSetting = " + BoolSetting;
}
}
public class MyStringSettings : MySettings
{
public string StringSetting { get; set; }
public override string GetSettingsString()
{
return "StringSetting = " + StringSetting;
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication3.EditMyObjectDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EditMyObjectDialog" Height="350" Width="350">
<StackPanel Margin="20">
<TextBlock Text="Title" />
<TextBox Name="txtTitle" />
<RadioButton Name="rdBoolSettings" Content="BoolSettings" IsChecked="True" Margin="0, 20, 0, 0" />
<CheckBox Name="chBool" Content="True" Margin="20, 0, 0, 20" />
<RadioButton Name="rdStringSettings" Content="StringSettings" />
<TextBox Name="txtString" Margin="20, 0, 0, 20"/>
<Button Content="OK" Click="OK_click" />
<Button Content="Cancel" Click="Cancel_click" Margin="0, 10" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace WpfApplication3
{
public partial class EditMyObjectDialog : Window
{
public MyObject Result { get; set; }
public EditMyObjectDialog(MyObject objectToEdit)
{
InitializeComponent();
txtTitle.Text = objectToEdit.Title;
if (objectToEdit.Settings is MyBoolSettings)
{
rdBoolSettings.IsChecked = true;
chBool.IsChecked = (objectToEdit.Settings as MyBoolSettings).BoolSetting;
}
if (objectToEdit.Settings is MyStringSettings)
{
rdBoolSettings.IsChecked = true;
txtString.Text = (objectToEdit.Settings as MyStringSettings).StringSetting;
}
}
private void OK_click(object sender, RoutedEventArgs e)
{
Result = new MyObject() { Title = txtTitle.Text };
if (rdBoolSettings.IsChecked == true)
Result.Settings = new MyBoolSettings() { BoolSetting = chBool.IsChecked == true };
if (rdStringSettings.IsChecked == true)
Result.Settings = new MyStringSettings() { StringSetting = txtString.Text };
DialogResult = true;
}
private void Cancel_click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
}
ExternalCode:
var f = new EditMyObjectDialog(myObject);
if (f.ShowDialog() == true)
myObject = f.Result;
I belive there is much better design pattern that uses data binding etc. So basically I have two questions.
How to make data binding not to
modify object until user hits 'OK'?
How to correctly handle 'Settings'
property? What to do when user
switches setting's type?
What I believe you're looking for is a combination of DataBinding and DataTemplating. DataTemplating will allow you to define different visual elements for different business objects (in this case MyBooleanSettings and MyStringSettings. DataBinding will allow the visual elements to update and be updated my the data in the business objects.
Example (xaml):
<Window DataContext={Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<DataTemplate DataType={x:Type local:MyObject}">
<TextBlock Text={Binding Title}" />
<ContentPresenter Content="{Binding Settings}" />
</DataTemplate>
<DataTemplate DataType={x:Type local:MyObject}">
<TextBox Text={Binding
</DataTemplate>
<DataTemplate DataType={x:Type local:MyBoolSettings}>
<CheckBox IsChecked="{Binding BoolSetting}" />
</DataTemplate>
<DataTemplate DataType={x:Type local:MyStringSettings}>
<TextBox Text="{Binding StringSetting}" />
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding ObjectToEdit}" />
</Window>
Then in the code behind define:
public MyObject ObjectToEdit { get; set; }
Finally update your objects:
public class MySettings : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(sting s)
{
if(PropertyChanged != null)
{
PropertyChanged(s);
}
}
}
public class BoolSettings : MySettings
{
bool _value;
bool BoolSetting
{
get { return _value; }
set
{
if(_value != value)
{
_value = value;
OnPropertyChanged("BoolSetting");
}
}
}
}
If however you really need to control when the view and object sync you should use the UpdateSourceTrigger property on the corresponding bindings.
If you want some additional reading I recommend: http://msdn.microsoft.com/en-us/library/ms752347.aspx
DataBinding is Simple . You can create an instance of MyObject and assign it to the DataContext property of the Form.
this.DataContext=MyObject;
And define binding for individual elements.
<TextBox Name="txtTitle" Text="{Binding Path=Title,Mode=TwoWay }" />
Setting mode as two way will affect the object as you make change in UI. One way will show the values.
How to make data binding not to modify object until user hits 'OK'?
Create a copy of the MyObject instance. In the Result property get method, return copy if user hit cancel (return unchanged copy) or if user hit OK, return the changed MyObject instance.
How to correctly handle 'Settings' property? What to do when user switches setting's type?
Whats the problem?

Categories

Resources