The SelectedValue property is never assigned. I don't know what I'm doing wrong

Rod At Work 866 Reputation points
2020-12-08T21:04:03.807+00:00

I've been banging my head on this WPF issue for a couple days. Everything looks fine to me, so I don't understand what I'm doing wrong. I had to change the code, which had been working fine, to satisfy a business requirement. When adding a new record to the table the WPF ComboBox has to show the text "Select...". I've asked around for guidance as to how to do that. The best solution I found, on Stack Overflow, was to use a ContentControl. This is what I have now:

<ContentControl DataContext="{Binding}">
    <ContentControl.ContentTemplate>
        <DataTemplate>
            <Grid>
                <ComboBox  Width="200"
                           x:Name="InstrumentModelComboBox"
                           IsSynchronizedWithCurrentItem="True"
                           ItemsSource="{Binding InstrumentModelList}"
                           DisplayMemberPath="Model"
                           SelectedValuePath="ID"
                           SelectedValue="{Binding InstrumentModelID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                </ComboBox>
                <TextBlock x:Name="InstrumentModelTextBlock"
                           Text="Select..."
                           IsHitTestVisible="False"
                           Visibility="Collapsed" />
            </Grid>
            <DataTemplate.Triggers>
                <Trigger SourceName="InstrumentModelComboBox"
                         Property="SelectedItem"
                         Value="{x:Null}">
                    <Setter TargetName="InstrumentModelTextBlock"
                            Property="Visibility"
                            Value="Visible" />
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

This works fine when the user wishes to create a new record. Exactly what it was supposed to do. But it fails to show an existing record when the user wishes to edit the record. Instead, it still shows the TextBlock with "Select..." in it.

My guess is I've done something wrong but am not sure where. Here's the InstrumentModelID property from the viewmodel:

public long? InstrumentModelID 
{ 
    get 
    {
        if (Course != null)
        {
            return Course.InstrumentModelID;
        }
        return 0;
    }
    set
    {
        Course.InstrumentModelID = value;
        RaisePropertyChanged();
    }
}

I've set a breakpoint both in the first line of the setter and the getter. The setter always gets called. But the getter never gets called, as I would expect it to be because of my referencing it in the XAML.

I'm working with VS 2019. We're using .NET Framework 4.5.2.

So, what's the mistake that I'm making, please?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,700 questions
{count} votes

3 answers

Sort by: Most helpful
  1. DaisyTian-1203 11,616 Reputation points
    2020-12-09T06:20:17.693+00:00

    Depending on your code, you might want to add a watermark to ComboBox, Below is my demo for you:

    Step 1: Add Microsoft.Xaml.Behaviors.Wpf in your project NuGet package Manager
    Step 2: Add xmlns:i="http://schemas.microsoft.com/xaml/behaviors" for the xaml and its code is:

      <ComboBox Width="200" Height="60" IsEditable="True">  
                <i:Interaction.Behaviors>  
                    <local:WatermarkBehavior Text="Please select..." />  
                </i:Interaction.Behaviors>  
                <ComboBoxItem>Item1</ComboBoxItem>  
                <ComboBoxItem>Item2</ComboBoxItem>  
                <ComboBoxItem>Item3</ComboBoxItem>  
            </ComboBox>  
    

    Step 3: The Code for the cs is:

     public class WatermarkBehavior : Behavior<ComboBox>  
        {  
            private WaterMarkAdorner adorner;  
      
            public string Text  
            {  
                get { return (string)GetValue(TextProperty); }  
                set { SetValue(TextProperty, value); }  
            }  
      
            public static readonly DependencyProperty TextProperty =  
                DependencyProperty.Register("Text", typeof(string), typeof(WatermarkBehavior), new PropertyMetadata("Watermark"));  
      
            protected override void OnAttached()  
            {  
                adorner = new WaterMarkAdorner(this.AssociatedObject, this.Text);  
      
                this.AssociatedObject.Loaded += this.OnLoaded;  
                this.AssociatedObject.GotFocus += this.OnFocus;  
                this.AssociatedObject.LostFocus += this.OnLostFocus;  
            }  
      
            private void OnLoaded(object sender, RoutedEventArgs e)  
            {  
                if (!this.AssociatedObject.IsFocused)  
                {  
                    if (String.IsNullOrEmpty(this.AssociatedObject.Text))  
                    {  
                        var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);  
                        layer.Add(adorner);  
                    }  
                }  
            }  
      
            private void OnLostFocus(object sender, RoutedEventArgs e)  
            {  
                if (String.IsNullOrEmpty(this.AssociatedObject.Text))  
                {  
                    try  
                    {  
                        var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);  
                        layer.Add(adorner);  
                    }  
                    catch { }  
                }  
            }  
      
            private void OnFocus(object sender, RoutedEventArgs e)  
            {  
                var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);  
                layer.Remove(adorner);  
            }  
      
            public class WaterMarkAdorner : Adorner  
            {  
                private string text;  
      
                public WaterMarkAdorner(UIElement element, string text) : base(element)  
                {  
                    this.IsHitTestVisible = false;  
                    this.Opacity = 0.6;  
                    this.text = text;                
                }  
      
                protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)  
                {  
                    base.OnRender(drawingContext);  
                    var text = new FormattedText(  
                            this.text,   
                            System.Globalization.CultureInfo.CurrentCulture,  
                            System.Windows.FlowDirection.LeftToRight,  
                            new System.Windows.Media.Typeface("Segoe UI"),  
                            12,  
                            Brushes.Black  
                         );  
                    drawingContext.DrawText(text, new Point(3, 3));  
                }  
            }  
        }  
    

    Step 4: The result picture is:
    46504-2.gif

    By the way, if you want to implement in a very easy way, you may think about below code:

      <ComboBox Width="120" Height="60" Text="Select....." IsEditable="True" IsReadOnly="True">  
                <ComboBoxItem>Item1</ComboBoxItem>  
                <ComboBoxItem>Item2</ComboBoxItem>  
                <ComboBoxItem>Item3</ComboBoxItem>  
            </ComboBox>  
    

    If I misunderstand your question,please point out.


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

  2. Rod At Work 866 Reputation points
    2020-12-09T17:27:33.33+00:00

    Hi @DaisyTian-MSFT, your answer helped me get there. I did have to make a change. Instead of your xmlns for bringing in the namespace aliased as "i", I had to use this:

    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"  
    

    My guess is that, because I'm working with .NET Framework 4.5.2, it's older than what you suggested. If that's wrong, please let me know.

    Here's what I put in for my ComboBox:

    <ComboBox  Width="200"  
     IsSynchronizedWithCurrentItem="True"  
     ItemsSource="{Binding InstrumentModelList}"  
     DisplayMemberPath="Model"  
     SelectedValuePath="ID"  
     SelectedValue="{Binding InstrumentModelID, UpdateSourceTrigger=PropertyChanged}">  
     <i:Interaction.Behaviors>  
     <common:WatermarkBehavior Text="Select..." />  
     </i:Interaction.Behaviors>  
    </ComboBox>  
    

    Also, I had to make a minor change to the OnLoaded() method in your WatermarkBehavior class:

    private void OnLoaded(object sender, RoutedEventArgs e)  
    {  
    	if (!this.AssociatedObject.IsFocused)  
    	{  
    		if (String.IsNullOrEmpty(this.AssociatedObject.Text))  
    		{  
    			var layer = AdornerLayer.GetAdornerLayer(this.AssociatedObject);  
    			if (layer != null)  
    			{  
    				layer.Add(adorner);  
    			}  
    		}  
    	}  
    }  
      
    

  3. Rod Falanga 566 Reputation points
    2020-12-10T03:09:29.33+00:00

    One thing I noticed when experimenting with it. Sometimes, if I went back and forth between the summary view (a datagrid on one tab) and the "detail view" (all the fields from the selected record in the datagrid from the first tab, displayed on the second tab), the watermark would appear over the top of the selected record.

    Obviously, that's not what we want to have happen. And I think a lot of the reason it does that is because we must leave all the data in the datagrid on the first tab, then display details in the second tab of a tab control, everything is still in memory. I would think that this wouldn't be a problem if we had separate views and viewmodels for the summary (datagrid) view and the detail view. But those who are in charge won't agree with that approach; everyything must be in one view and viewmodel, always active, having to track what tab the user is on, etc.

    Given the approach they insist happen and aren't willing to consider any other approach, how do I avoid the watermark appearing over the top of a selected instrument?