使用ComboBox SelectedItem获得CRAZY

时间:2013-04-26 11:40:13

标签: c# wpf xaml data-binding mvvm

我有一个数据绑定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));
}

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

enter image description here

然后我打开一个或多个流程实例,然后单击“刷新”按钮。第一个被ComboBox选为默认值...我可以选择一个或选择另一个,这没关系:

enter image description here

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

enter image description here

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

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

http://www.filedropper.com/damncb

4 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:1)

您没有清除ObservableCollection。试试这个:

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

    // remaining of your logic;
}

答案 2 :(得分:1)

很少有人指出解决方案

  1. 尝试清除值而不是取消它们,以便在触发PropertyChanged事件时,WPF UI元素不会失去对实例的绑定..
  2. 根据第1点,为SelectedItem属性设置Text内部为值'',或者无论你是否已经包装,而不是使整个实例无效并每次都重新创建。
  3. 嗯..你还没有展示combobox.selectedItem如何从你的包装类ToString()获得ProcessInstance或类似内容,所以我无法从代码中发布逻辑更改透视图。

答案 3 :(得分: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();
}