3

我有一个ComboBox看起来像这样的数据绑定:

<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"/>

然后,在我的 MainWindow.xaml.cs 中:

public MainWindow()
{
    InitializeComponent();
    DataContext = m_ViewModel = new ViewModel();
}

private void ClickRefresh(Object sender, RoutedEventArgs e)
{
    m_ViewModel.Populate();
}

这是我的 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;
    }
}

这是我的 ProcessInstance 类相关代码:

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));
}

现在发生了什么......我在不存在流程实例的情况下启动应用程序:

在此处输入图像描述

然后我打开一个或多个流程实例并单击刷新按钮。第一个由 ComboBox 默认选择...我可以选择那个或选择另一个,没关系:

在此处输入图像描述

现在我关闭每个流程实例,然后再次单击刷新按钮。在这种情况下发生的情况是 Populate() 方法将 SelectedItem 和 Items 都设置为 null,因此 ComboBox 看起来是空的,然后它禁用 ComboBox 并使水印标签可见。但这是我得到的:

在此处输入图像描述

之前的 SelectedItem 仍然存在。为什么?为什么?!?!

[编辑] 这是下载项目的链接:

http://www.filedropper.com/damncb

4

4 回答 4

3

您需要清除 上的SelectedValue属性ComboBox,或者可能设置SelectedIndex为 -1

于 2013-04-26T11:54:54.473 回答
1

你没有清除你的ObservableCollection. 试试这个 :

if (processInstances.Count == 0)
{
    Items.Clear();

    // remaining of your logic;
}
于 2013-04-26T11:50:20.797 回答
1

几个点来解决

  1. 尝试清除值而不是使它们无效,以便 WPF UI 元素在PropertyChanged触发事件时不会失去与实例的绑定。
  2. 根据第 1 点,将属性的SelectedItem内部设置为值 ''Text或您已包装的任何内容,而不是使整个实例无效并每次都重新创建新实例。
  3. 嗯..您还没有展示如何从您的包装类中combobox.selectedItem获取或类似的东西,所以我无法从代码角度发布逻辑更改。ToString()ProcessInstance
于 2013-04-26T12:09:20.727 回答
1

由于版本的原因,我无法运行您的测试项目,但我将ViewModel类和 XAML 复制到了一个新的测试项目,这一切似乎都按预期工作,选定的项目被清除,组合框禁用,并且水印出现时按钮被点击。

所以我猜你的环境中的某些东西正在影响它,或者它是你的代码中没有立即显现的其他东西。

为了找出哪个,我在下面发布了我用来测试的(正确工作的)代码。

如果这不起作用,那么问题与您的环境有关。我正在使用 VS2010、.Net 4.0 和 Windows 7。

如果是这样,那么您的代码中的其他地方肯定发生了一些在您的测试示例中没有立即显现的东西。我会检查任何异步代码以查看 PropertyChange 通知是否发生在后台线程上,因为有时这不会触发 UI 线程上的更新。

对于由此产生的代码转储,我提前道歉:)

该类ViewModel是从测试项目中复制的,并进行了查找/替换以更改"BrowserInstance"为“ViewModelBase”以适合我的测试应用程序。一些不必要的部分也被注释掉或稍微修改以适应我的测试应用程序。

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
}

我的测试应用程序中的ViewModelBase类如下所示:

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
}

Test测试类是这样的:

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);
            }
        }
    }
}

我的测试 XAML 看起来像这样。我添加了一个最小高度,因为它在我的测试样式中没有正确显示,并且添加了背景颜色只是为了查看它的渲染位置。

<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>

最后,我的 XAML 背后的代码:

ViewModel m_ViewModel;

public MainWindow()
{
    InitializeComponent();
    DataContext = m_ViewModel = new ViewModel();
}

private void Button_Click_1(object sender, RoutedEventArgs e)
{
    m_ViewModel.Populate();
}
于 2013-04-26T17:23:34.633 回答