Reputation: 23675
I have a data bound ComboBox
that looks like this:
<ComboBox Canvas.Left="5" Canvas.Top="5" IsEnabled="{Binding Path=ComboBoxEnabled}" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Mode=TwoWay, Path=SelectedItem}" Width="250">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}" Text="{Binding}" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Canvas.Left="5" Canvas.Top="4" TextOptions.TextFormattingMode="Display" Content="No process instances have been found." Height="{DynamicResource {x:Static SystemParameters.WindowCaptionHeightKey}}" IsEnabled="False" Visibility="{Binding Path=WatermarkVisibility}" Width="250"/>
<Button Canvas.Right="5" Canvas.Top="5" Click="ClickRefresh" Content="Refresh" Width="75"/>
Then, in my MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
DataContext = m_ViewModel = new ViewModel();
}
private void ClickRefresh(Object sender, RoutedEventArgs e)
{
m_ViewModel.Populate();
}
Here is my ViewModel.cs:
public ProcessInstance SelectedItem
{
get { return m_SelectedItem; }
set
{
if (m_SelectedItem != value)
{
m_SelectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
}
public ObservableCollection<ProcessInstance> Items
{
get { return m_Items; }
private set
{
if (m_Items != value)
{
m_Items = value;
NotifyPropertyChanged("Items");
}
}
}
public ViewModel()
{
Populate();
}
public void Populate()
{
ProcessInstance selectedItem = m_SelectedItem;
SelectedItem = null;
Items = null;
List<ProcessInstance> processInstances = new List<ProcessInstance>();
foreach (Process process in Process.GetProcesses())
{
if (...)
processInstances.Add(new ProcessInstance(process));
}
if (processInstances.Count == 0)
{
ComboBoxEnabled = false;
WatermarkVisibility = Visibility.Visible;
}
else
{
Items = new ObservableCollection<ProcessInstance>(processInstances.OrderBy(x => x.Process.Id));
if (selectedItem != null)
SelectedItem = m_Items.SingleOrDefault(x => x.ProcessEquals(selectedItem));
if (m_SelectedItem == null)
SelectedItem = m_Items[0];
ComboBoxEnabled = true;
WatermarkVisibility = Visibility.Hidden;
}
}
And here is my ProcessInstance class relevant code:
public override Boolean Equals(Object obj)
{
return Equals(obj as ProcessInstance);
}
public override Int32 GetHashCode()
{
Int32 hashCode;
if ((m_Process == null) || m_Process.HasExited)
hashCode = 0;
else
{
hashCode = (m_Process.Id.GetHashCode() * 397) ^ m_Process.MainModule.BaseAddress.GetHashCode();
if (!String.IsNullOrEmpty(m_Process.MainWindowTitle))
hashCode = (hashCode * 397) ^ m_Process.MainWindowTitle.GetHashCode();
}
return hashCode;
}
public override String ToString()
{
String processId = process.Id.ToString("X8", CultureInfo.CurrentCulture);
String windowTitle = (process.MainWindowTitle.Length > 0) ? process.MainWindowTitle : "NULL";
return String.Format(CultureInfo.CurrentCulture, "[{0}] {1} - {2}", type, processId, windowTitle);
}
public Boolean Equals(ProcessInstance other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (m_Process == null)
{
if (other.Process == null)
return true;
return false;
}
if (other.Process == null)
return false;
return ((m_Process.Id == other.Process.Id) && (m_Process.MainModule.BaseAddress == other.Process.MainModule.BaseAddress) && (m_Process.MainWindowTitle == other.Process.MainWindowTitle));
}
public Boolean ProcessEquals(ProcessInstance other)
{
if (other == null)
throw new ArgumentNullException("other");
if (m_Process == null)
return (other.Process == null);
if (other.Process == null)
return false;
return ((m_Process.Id == other.Process.Id) && (m_Process.MainModule.BaseAddress == other.Process.MainModule.BaseAddress));
}
Now here is what happens... I start the application while no process instances exist:
Then I open one or more process instances and I click the Refresh button. The first one is selected by ComboBox as default... I can keep that one selected or select another one, it doesn't matter:
Now i close every process instance and I click Refresh button again. What happens in this case is that the Populate() method sets both SelectedItem and Items to null, so the ComboBox looks empty, and then it disables the ComboBox and makes the watermark Label visible. But here is what I get:
The previous SelectedItem is still there. Why? WHY?!?!
[EDIT] HERE IS A LINK TO DOWNLOAD THE PROJECT:
http://www.filedropper.com/damncb
Upvotes: 3
Views: 713
Reputation: 132548
I'm unable to run your test project because of the version, but I copied the ViewModel
class and XAML to a new test project and it all seems to work as expected, with the selected item being cleared, combobox disabling, and watermark appearing when the button is clicked.
So I would guess that either something in your environment is affecting it, or it is something else that isn't immediately apparent with your code.
To find out which, I have posted the (correctly working) code that I am using to test below.
If this doesn't work, then the problem has something to do with your environment. I'm using VS2010, .Net 4.0, and Windows 7.
If it does, then you must have something occurring elsewhere in your code that is not immediately apparent in your test examples. I would check for any async code to see if the PropertyChange notifications are occurring on a background thread, as sometimes that doesn't trigger the update on the UI thread.
I apologize in advance for the resulting code dump :)
The ViewModel
class was copied from test project, and a find/replace done to change "BrowserInstance"
to "ViewModelBase" to fit my testing app. Some unnecessary sections were also commented out or modified slightly to accommodate my test app.
public sealed class ViewModel : INotifyPropertyChanged
{
#region Members: Fields
private Boolean m_ButtonAttachEnabled;
private Boolean m_ButtonDetachEnabled;
private Boolean m_ButtonRefreshEnabled;
private Boolean m_ComboBoxEnabled;
private Boolean m_ProgressBarEnabled;
private ViewModelBase m_SelectedItem;
private Dictionary<String, PropertyChangedEventArgs> m_PropertyChangedEventArgs = new Dictionary<String, PropertyChangedEventArgs>();
private Double m_ProgressBarMaximum;
private Double m_ProgressBarValue;
private IntPtr m_ProcessHandle;
private IntPtr m_ProcessHook;
private ObservableCollection<ViewModelBase> m_Items;
private Visibility m_WatermarkVisibility;
#endregion
#region Members: INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Properties
public Boolean ButtonAttachEnabled
{
get { return m_ButtonAttachEnabled; }
private set
{
if (m_ButtonAttachEnabled != value)
{
m_ButtonAttachEnabled = value;
NotifyPropertyChanged("ButtonAttachEnabled");
}
}
}
public Boolean ButtonDetachEnabled
{
get { return m_ButtonDetachEnabled; }
private set
{
if (m_ButtonDetachEnabled != value)
{
m_ButtonDetachEnabled = value;
NotifyPropertyChanged("ButtonDetachEnabled");
}
}
}
public Boolean ButtonRefreshEnabled
{
get { return m_ButtonRefreshEnabled; }
private set
{
if (m_ButtonRefreshEnabled != value)
{
m_ButtonRefreshEnabled = value;
NotifyPropertyChanged("ButtonRefreshEnabled");
}
}
}
public Boolean ComboBoxEnabled
{
get { return m_ComboBoxEnabled; }
private set
{
if (m_ComboBoxEnabled != value)
{
m_ComboBoxEnabled = value;
NotifyPropertyChanged("ComboBoxEnabled");
}
}
}
public Boolean ProgressBarEnabled
{
get { return m_ProgressBarEnabled; }
private set
{
if (m_ProgressBarEnabled != value)
{
m_ProgressBarEnabled = value;
NotifyPropertyChanged("ProgressBarEnabled");
}
}
}
public ViewModelBase SelectedItem
{
get { return m_SelectedItem; }
set
{
if (m_SelectedItem != value)
{
m_SelectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
}
public Double ProgressBarMaximum
{
get { return m_ProgressBarMaximum; }
private set
{
if (m_ProgressBarMaximum != value)
{
m_ProgressBarMaximum = value;
NotifyPropertyChanged("ProgressBarMaximum");
}
}
}
public Double ProgressBarValue
{
get { return m_ProgressBarValue; }
private set
{
if (m_ProgressBarValue != value)
{
m_ProgressBarValue = value;
NotifyPropertyChanged("ProgressBarValue");
}
}
}
public ObservableCollection<ViewModelBase> Items
{
get { return m_Items; }
private set
{
if (m_Items != value)
{
m_Items = value;
NotifyPropertyChanged("Items");
}
}
}
public Visibility WatermarkVisibility
{
get { return m_WatermarkVisibility; }
private set
{
if (m_WatermarkVisibility != value)
{
m_WatermarkVisibility = value;
NotifyPropertyChanged("WatermarkVisibility");
}
}
}
#endregion
#region Constructors
public ViewModel()
{
m_PropertyChangedEventArgs = new Dictionary<String, PropertyChangedEventArgs>();
Populate();
}
#endregion
#region Methods: Functions
private PropertyChangedEventArgs GetPropertyChangedEventArgs(String propertyName)
{
PropertyChangedEventArgs propertyChangedEventArgs;
if (!m_PropertyChangedEventArgs.TryGetValue(propertyName, out propertyChangedEventArgs))
m_PropertyChangedEventArgs[propertyName] = propertyChangedEventArgs = new PropertyChangedEventArgs(propertyName);
return propertyChangedEventArgs;
}
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, GetPropertyChangedEventArgs(propertyName));
}
private void ResetProgressBar(Int32 maximumValue = 100)
{
ProgressBarMaximum = maximumValue;
ProgressBarValue = 0;
}
private void SetInterfaceStatus(Status status)
{
switch (status)
{
case Status.Attached:
{
ButtonAttachEnabled = false;
ButtonDetachEnabled = true;
ButtonRefreshEnabled = false;
ComboBoxEnabled = false;
ProgressBarEnabled = false;
WatermarkVisibility = Visibility.Hidden;
break;
}
case Status.Busy:
{
ButtonAttachEnabled = false;
ButtonDetachEnabled = false;
ButtonRefreshEnabled = false;
ComboBoxEnabled = false;
ProgressBarEnabled = true;
WatermarkVisibility = Visibility.Hidden;
break;
}
case Status.Detached:
{
ButtonAttachEnabled = true;
ButtonDetachEnabled = false;
ButtonRefreshEnabled = true;
ComboBoxEnabled = true;
ProgressBarEnabled = true;
WatermarkVisibility = Visibility.Hidden;
goto default;
}
case Status.DetachedEmpty:
{
ButtonAttachEnabled = false;
ButtonDetachEnabled = false;
ButtonRefreshEnabled = true;
ComboBoxEnabled = false;
ProgressBarEnabled = false;
WatermarkVisibility = Visibility.Visible;
goto default;
}
default:
ResetProgressBar();
break;
}
}
public void Attach()
{
}
public void Detach()
{
}
public void Populate()
{
ViewModelBase selectedItem = m_SelectedItem;
SelectedItem = null;
Items = new ObservableCollection<ViewModelBase>();
List<ViewModelBase> ViewModelBases = new List<ViewModelBase>();
if (selectedItem == null)
{
ViewModelBases.Add(new Test { TestValue = "123456789", TestEnum = TestEnum.A, TestBool = false, TestBool2 = true });
ViewModelBases.Add(new Test { TestValue = "987365321", TestEnum = TestEnum.B, TestBool = true, TestBool2 = true });
ViewModelBases.Add(new Test { TestValue = "784512457", TestEnum = TestEnum.B, TestBool = true, TestBool2 = true });
}
//foreach (Process process in Process.GetProcesses())
//{
// if ((process.ProcessName == "chrome") && !process.HasExited && (process.MainModule.ModuleName == "chrome.exe"))
// ViewModelBases.Add(new ViewModelBase(ViewModelBaseType.Chrome, process));
//}
if (ViewModelBases.Count == 0)
SetInterfaceStatus(Status.DetachedEmpty);
else
{
Items = new ObservableCollection<ViewModelBase>(ViewModelBases);
if (selectedItem != null)
SelectedItem = m_Items.SingleOrDefault(x => x.Equals(selectedItem));
if (m_SelectedItem == null)
SelectedItem = m_Items[0];
SetInterfaceStatus(Status.Detached);
}
}
#endregion
#region Enumerators
private enum Status
{
Attached,
Busy,
Detached,
DetachedEmpty
}
#endregion
}
The ViewModelBase
class from my test app looks like this:
public class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
{
// Fields
private PropertyChangedEventHandler propertyChanged;
// Events
public event PropertyChangedEventHandler PropertyChanged
{
add
{
PropertyChangedEventHandler handler2;
PropertyChangedEventHandler propertyChanged = this.propertyChanged;
do
{
handler2 = propertyChanged;
PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler)Delegate.Combine(handler2, value);
propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);
}
while (propertyChanged != handler2);
}
remove
{
PropertyChangedEventHandler handler2;
PropertyChangedEventHandler propertyChanged = this.propertyChanged;
do
{
handler2 = propertyChanged;
PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler)Delegate.Remove(handler2, value);
propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);
}
while (propertyChanged != handler2);
}
}
protected void RaisePropertyChanged(params string[] propertyNames)
{
if (propertyNames == null)
{
throw new ArgumentNullException("propertyNames");
}
foreach (string str in propertyNames)
{
this.RaisePropertyChanged(str);
}
}
protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
string propertyName = PropertySupport.ExtractPropertyName<T>(propertyExpression);
this.RaisePropertyChanged(propertyName);
}
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.propertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated
/// </summary>
//protected List<string> ValidatedProperties = new List<string>();
#region Validation Delegate
public delegate string ValidationDelegate(
object sender, string propertyName);
private List<ValidationDelegate> _validationDelegates =
new List<ValidationDelegate>();
public void AddValidationDelegate(ValidationDelegate func)
{
_validationDelegates.Add(func);
}
public void RemoveValidationDelegate(ValidationDelegate func)
{
if (_validationDelegates.Contains(func))
_validationDelegates.Remove(func);
}
#endregion // Validation Delegate
public virtual string GetValidationError(string propertyName)
{
// If user specified properties to validate, check to see if this one exists in the list
//if (ValidatedProperties.IndexOf(propertyName) < 0)
//{
// return null;
//}
string s = null;
//switch (propertyName)
//{
//}
foreach (var func in _validationDelegates)
{
s = func(this, propertyName);
if (s != null)
return s;
}
return s;
}
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
//public bool IsValid
//{
// get
// {
// return (GetValidationError() == null);
// }
//}
//public string GetValidationError()
//{
// string error = null;
// if (ValidatedProperties != null)
// {
// foreach (string s in ValidatedProperties)
// {
// error = GetValidationError(s);
// if (error != null)
// {
// return error;
// }
// }
// }
// return error;
//}
#endregion // IDataErrorInfo & Validation Members
}
And the Test
class for testing is this:
public class Test : ViewModelBase
{
public int Id { get; set; }
private TestEnum _testEnum;
private string _testValue = "Testing";
private double _testNumber = 10000;
private bool _testBool;
private bool _testBool2;
private ObservableCollection<int> _someCollection;
public ObservableCollection<int> SomeCollection
{
get
{
if (_someCollection == null)
_someCollection = new ObservableCollection<int>();
return _someCollection;
}
}
public Test()
{
//this.ValidatedProperties.Add("TestValue");
//this.ValidatedProperties.Add("TestNumber");
SomeCollection.Add(1);
SomeCollection.Add(2);
SomeCollection.Add(3);
}
public override string ToString()
{
return TestValue;
}
public void RaisePropertyChanged1(string property)
{
base.RaisePropertyChanged(property);
}
//public override string GetValidationError(string propertyName)
//{
// // If user specified properties to validate, check to see if this one exists in the list
// if (ValidatedProperties.IndexOf(propertyName) < 0)
// {
// return null;
// }
// string s = base.GetValidationError(propertyName); ;
// switch (propertyName)
// {
// case "TestValue":
// s = "error";
// break;
// }
// return s;
//}
public TestEnum TestEnum
{
get { return _testEnum; }
set
{
if (value != _testEnum)
{
_testEnum = value;
base.RaisePropertyChanged(() => this.TestEnum);
}
}
}
private ObservableCollection<int> _test;
public ObservableCollection<int> Test1
{
get
{
if (_test == null)
{
_test = new ObservableCollection<int>();
for (int i = 0; i < 5; i++)
{
_test.Add(i);
}
}
return _test;
}
set
{
if (value != _test)
{
_test = value;
RaisePropertyChanged(() => this.Test1);
}
}
}
public string TestValue
{
get { return _testValue; }
set
{
if (value != _testValue)
{
_testValue = value;
base.RaisePropertyChanged(() => this.TestValue);
}
}
}
public bool TestBool
{
get { return _testBool; }
set
{
if (value != _testBool)
{
_testBool = value;
base.RaisePropertyChanged(() => this.TestBool);
}
}
}
public bool TestBool2
{
get { return _testBool2; }
set
{
if (value != _testBool2)
{
_testBool2 = value;
base.RaisePropertyChanged(() => this.TestBool);
}
}
}
public double TestNumber
{
get { return _testNumber; }
set
{
if (value != _testNumber)
{
_testNumber = value;
base.RaisePropertyChanged(() => this.TestNumber);
}
}
}
}
My test XAML was looked like this. I had added a min-height because it wasn't showing up correctly with my test styles, and a background color was added just to see where it was rendering at.
<Grid MinHeight="100" Background="Lavender">
<Canvas Background="Red">
<GroupBox Canvas.Left="10" Canvas.Top="5" BorderBrush="Black" Header="Test" Height="86" Width="472">
<Canvas>
<ComboBox Canvas.Left="5" Canvas.Top="5" IsEnabled="{Binding Path=ComboBoxEnabled}" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Mode=TwoWay, Path=SelectedItem}" Width="366">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}" Text="{Binding }" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Canvas.Left="5" Canvas.Top="4" TextOptions.TextFormattingMode="Display" Content="This is a Test" Height="50" IsEnabled="False" Visibility="{Binding Path=WatermarkVisibility}" Width="366"/>
<Button Canvas.Right="5" Canvas.Top="5" Click="Button_Click_1" Content="Refresh" IsEnabled="{Binding Path=ButtonRefreshEnabled}" Width="75"/>
</Canvas>
</GroupBox>
</Canvas>
</Grid>
And last of all, the code behind my XAML:
ViewModel m_ViewModel;
public MainWindow()
{
InitializeComponent();
DataContext = m_ViewModel = new ViewModel();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
m_ViewModel.Populate();
}
Upvotes: 1
Reputation: 101
You are not clearing your ObservableCollection
. Try this :
if (processInstances.Count == 0)
{
Items.Clear();
// remaining of your logic;
}
Upvotes: 1
Reputation: 20451
You need to clear the SelectedValue
property on the ComboBox
, or maybe set SelectedIndex
to -1
Upvotes: 3
Reputation: 7249
Few points to hit the solution
PropertyChanged
events are triggered..SelectedItem
internals to value '' for the Text
property or whatsoever you have wrapped instead of nullifying entire instance and recreating new each time.combobox.selectedItem
is getting the ToString()
or something similar from the wrapper class ProcessInstance
of your so I could not post the change of logic from code perspective.Upvotes: 1