question

RodAtWork avatar image
0 Votes"
RodAtWork asked DaisyTian-1203 commented

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

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-wpf
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Selecteditem isn't selectedvalue.

I would try changing the trigger first and see what difference that makes.

1 Vote 1 ·
DaisyTian-1203 avatar image
0 Votes"
DaisyTian-1203 answered

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.



2.gif (55.4 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

RodAtWork avatar image
0 Votes"
RodAtWork answered RodAtWork commented

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);
             }
         }
     }
 }
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@RodAtWork Could you remove your IsSynchronizedWithCurrentItem="True" for ComboBox to try? I tested your code without using IsSynchronizedWithCurrentItem="True",and everything is ok. By the way,my test project is also Framework 4.5.2, and I add Microsoft.Xaml.Behaviors.Wpf in Visual Studio 19.

0 Votes 0 ·

@DaisyTian-MSFT removing the IsSynchronizedWithCurrentItem="True" doesn't work. When bringing up an existing record, the ComboBox doesn't show the previously selected item.

0 Votes 0 ·
RodFalanga avatar image
0 Votes"
RodFalanga answered DaisyTian-1203 commented

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?

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@RodFalanga I can't reproduce the error with your code snippets, could you show me a demo in GitHub/OneDriver which can reproduce your error for me to analyze?

0 Votes 0 ·