none
DataTrigger not working

    Question

  • Hi,

    I am having issues with using DataTrigger in my WPF app. My app is setup as following:

    1. MainWindow has a Tab Control and a  couple of buttons (Save, Exit etc.). 

    2. I have separate UserControl classes for each of the tab item with various UI elements on it.  Also for each UserControl, I created a separate class (TabData) to read/write values from the database for that particular tab item and which also implements INotifyPropertyChanged.  I also created separate properties for for change notification to detect user actions on each UI element. I am using data binding to attach these properties to UI elements.

    3. There is a LoadData() method in each UserControl class, where I set the DataContext of the parent grid on tab item to the instance of TabData which reads the database values and they're correctly shown in UI elements.  

    Everything works fine so far. When I change the value of a UI element, correct property changed event is raised in TabData class. Now I want to change the appearance of "Save" button in MainWindow class if user changes value of any UI element.  I tried to use DataTrigger to do that but its not working.  Ideally, I would like Save button to start blinking when some value is changed on any of the tab item.  I've seen many posts where DataTrigger has binding to a simple .NET property within the same class, but my scenario is different.

    Please help.  My code is given below:

    UserControl1.cs

            public void LoadTab3()
            {
                Tab3Data tab3data = App.Db.GetTab3();
                gridTab3.DataContext = tab3data;
            }

    TabData.cs

        public class Tab3Data : INotifyPropertyChanged
        {
            private bool dataChanged = false;
            public bool DataChanged
            {
                get { return dataChanged; }
                set
                {
                    dataChanged = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("DataChanged"));
                }
            }
            private string subCategory;
            public string SubCategory
            {
                get { return subCategory; }
                set
                {
                    subCategory = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("SubCategory"));
                    OnPropertyChanged(new PropertyChangedEventArgs("DataChanged"));
                }
            }
            private string currentValue;
            public string CurrentValue
            {
                get { return currentValue; }
                set
                {
                    currentValue = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("CurrentValue"));
                    OnPropertyChanged(new PropertyChangedEventArgs("DataChanged"));
                }
            }
            public Tab3Data(string subCategory, string curVal)
            {
                SubCategory = subCategory;
                CurrentValue = curVal;
            }
            public event PropertyChangedEventHandler PropertyChanged;
            public void OnPropertyChanged(PropertyChangedEventArgs e)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, e);
                }
            }
        }
    

    Monday, August 13, 2012 10:40 PM

Answers

  • I think what you did is good method, because if you want to pass data between Mainwindow and UserControl you have to pass an data object as DataContext, then use property changed event to notify your UI.


    Sheldon _Xiao[MSFT]
    MSDN Community Support | Feedback to us
    Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • Marked as answer by vg_jain Friday, August 17, 2012 3:20 PM
    Friday, August 17, 2012 2:19 AM
    Moderator

All replies

  • Let's assume that :
    - you set MainWindow's DataContext to an instance of class MainDC;
    - MainDC contains a property like 'DataChange' (with PropertyNotification) that you bind to the Save button for DataTrigger purposes
    - you inject 'MainDC' in 'Tab3Data' as in the code snippet below [this is the key aspect of the response]
    ---
    Now, each time a Tab3Data property changes, you can use the value of Tab3Data.MainDC and
    - either set MainDC.DataChange from within the Tab3Data.Property setter
    - or (better) invoke a MainDC.DataChangeMethod that will modify MainDC.DataChange for DataTrigger purposes of the SAVE button.
    ---
    In summary, MainDC aggregates all changes from the different TabControls to notify (and eventually reset) the SAVE button
    ---

    public void LoadTab3()
            {
                Tab3Data tab3data = App.Db.GetTab3();
                gridTab3.DataContext = tab3data;
                // Add the following to 'link' Tab3Data to MainDC
    	    MainDC mainDC = Application.Current.MainWindow.DataContext as MainDC;
    	    tab3data.MainDC = mainDC;
            }

    Tuesday, August 14, 2012 3:54 PM
  • Hi ForInfo,

    Thanks a lot for your response. I got your point, but I need more information.  I am not sure what you mean by the last statement from above:

     tab3data.MainDC = mainDC;

    Did you mean to keep a separate instance of MainDC in both MainWindow and Tab3Data class ?  Then above statement should be like this:

     tab3data.mainDC = mainDC;
     

    where mainDC is an instance of MainDC and a member of tab3Data class.  When I do the above, Data Trigger is still not working. Below is my complete code:

    MainWindow.xaml

    <Window x:Class="DemoUI.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:DemoUI"
            Title="MainWindow" Loaded="Window_Loaded" Background="SlateGray" FontSize="14">
        <Window.Resources>
            <local:StringToCheckboxIsChecked x:Key="myConverter"/>
        </Window.Resources>
        <DockPanel Margin="0">
            <Border DockPanel.Dock="Right" Margin="5"  BorderBrush="Black" BorderThickness="1">
                <StackPanel Orientation="Vertical" VerticalAlignment="Center">
                    <Button Content="Save" Click="btnSave_Click" Margin="10" Padding="5">
                        <Button.Style>
                            <Style>
                                <Style.Triggers>
                                    <DataTrigger
                                Binding="{Binding Path=DataChanged}" Value="True">
                                        <Setter Property="Button.Content" Value="Save Me"/>
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </Button.Style>
                    </Button>
                    <Button Content="Exit" Click="btnExit_Click" Margin="10" Padding="5"></Button>
                </StackPanel>
            </Border>
            <Border Margin="0" BorderBrush="Black"  BorderThickness="1">
                <TabControl x:Name="MainTab" TabStripPlacement="Left" ScrollViewer.VerticalScrollBarVisibility="Visible">
                    <TabItem Name="tab1" Header="1">
                    </TabItem>
                    <TabItem Name="tab2" Header="2">
                    </TabItem>
                    <TabItem Name="tab3" Header="3">
                        <TabItem.Content>
                            <local:UserControl1 x:Name="UC1"/>
                        </TabItem.Content>
                    </TabItem>
                </TabControl>
            </Border>
        </DockPanel>
    </Window>

    MainWindow.xaml.cs

       public partial class MainWindow : Window
        {
            public MainDC mainDC = new MainDC();
            public MainWindow()
            {
                InitializeComponent();
                DataContext = mainDC;
            }
       ....
           public class MainDC : INotifyPropertyChanged
           {
               private bool dataChanged = false;
               public bool DataChanged
               {
                   get { return dataChanged; }
                   set
                   {
                       dataChanged = value;
                       OnPropertyChanged(new PropertyChangedEventArgs("DataChanged"));
                   }
               }
               public event PropertyChangedEventHandler PropertyChanged;
               public void OnPropertyChanged(PropertyChangedEventArgs e)
               {
                   if (PropertyChanged != null)
                   {
                       //DataChanged = true;
                       PropertyChanged(this, e);
                   }
               }
           }

    UserControl1.cs

        public partial class UserControl1 : UserControl
        {
            public bool bDataContextChanged = false;
            public UserControl1()
            {
                InitializeComponent();
                App.Db.GetPreferences("TestCat1");
            }
            public void LoadTab3()
            {
                Tab3Data tab3data = App.Db.GetTab3("TestCat1");
                gridTab3.DataContext = tab3data;
                // Add the following to 'link' Tab3Data to MainDC
                MainDC mainDC = Application.Current.MainWindow.DataContext as MainDC;
                tab3data.mainDC = mainDC; 
            }
    	...
    	
        public class Tab3Data : INotifyPropertyChanged
        {
            public MainDC mainDC = new MainDC();
            private string subCategory;
            public string SubCategory
            {
                get { return subCategory; }
                set
                {
                    subCategory = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("SubCategory"));
                }
            }
    
            private string currentValue;
            public string CurrentValue
            {
                get { return currentValue; }
                set
                {
                    currentValue = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("CurrentValue"));
                    this.mainDC.DataChanged = true;
                }
            }
            private bool boolCurVal = false;
            public bool BoolCurVal
            {
                get { return boolCurVal; }
                set
                {
                    boolCurVal = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("BoolCurVal"));
                    OnPropertyChanged(new PropertyChangedEventArgs("DataChanged"));
                }
            }
            public Tab3Data(string subCategory, string curVal)
            {
                SubCategory = subCategory;
                CurrentValue = curVal;
            }
            public event PropertyChangedEventHandler PropertyChanged;
            public void OnPropertyChanged(PropertyChangedEventArgs e)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, e);
                }
            }
        }

    Thanks.

    Tuesday, August 14, 2012 5:31 PM
  • Hi vg_jain,

    1) you should set DataContext property before MainWindow InitializeComponent(), and then you could get correct value in your UserControl, refer to below code snippet:

        public MainDC mainDC = new MainDC();
        public MainWindow()
        {
            DataContext = mainDC;
            InitializeComponent();
            
    
        }

    Then you could get correct here:

     MainDC mainDC = Application.Current.MainWindow.DataContext as MainDC;
                tab3data.mainDC = mainDC;

    2) you should change your Button style to below:

    <Button Margin="10" Padding="5">
        <Button.Style>
            <Style TargetType="Button">
                <Setter Property="Content" Value="Save"/> 
                <Style.Triggers>
                    <DataTrigger
                Binding="{Binding Path=DataChanged}" Value="True">
                        <Setter Property="Button.Content" Value="Save Me"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>

    Then your DataTrigger will work well, and when you change CurrentValue/this.mainDC.DataChanged = true value, your DataTrigger will be fired.

    best regards,


    Sheldon _Xiao[MSFT]
    MSDN Community Support | Feedback to us
    Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Wednesday, August 15, 2012 5:51 AM
    Moderator
  • - Sheldon, thanks for clarifying this missing point!

    - VG, I finally wanted to mention that there is an alternative to the DataTrigger, whereby the Button is always visible, be it enabled or disabled according to the "DataChanged" property value. A matter of taste of course ...

            <Button Content="Save" IsEnabled="{Binding Path=DataChanged}"/>
    

     

    Wednesday, August 15, 2012 6:21 AM
  • Your code doesn't require datatrigger at all, you have made things complicated, use the below code:

    <Button Content={Binding buttonValue, mode=TwoWay}/>

    Instead of setting datachanged and using DataTrigger, you can update buttonValue with the new content.

    public string CurrentValue { get { return currentValue; } set { currentValue = value;

                    buttonValue = "Save Me";

    OnPropertyChanged(new PropertyChangedEventArgs("CurrentValue")); } }

    _________________________________________________________________________________________

    Do Vote and propose the answer

    • Proposed as answer by kishhr Wednesday, August 15, 2012 8:30 AM
    Wednesday, August 15, 2012 8:30 AM
  • Hi Sheldon / ForInfo / Kishhr,

    Thanks for your valuable comments. Now its working fine.

    Could you please explain why do I need to set DataContext property before MainWindow InitializeComponent() ?  Also, why do I need to set the TargetType to Button 

    <Style TargetType="Button">

    in the button style when I am setting this style inside that specific button.  What was wrong with my button Style code I posted earlier ?  Just want to learn...

    Could you please also post some sample code to animate the Save button so that it starts blinking between two colors when DataChanged is set to true after user changes any value ?  Save button should continue blinking until it is clicked (to Save new values into database) or application is closed. 

    I really appreciate all your help.

    Thanks.

    Wednesday, August 15, 2012 8:18 PM
  • Hi Vg_jain,

    -->why do I need to set DataContext property before MainWindow InitializeComponent() ?

    when MainWindow InitializeComponent, your UserControl will InitializeComponent before you set " DataContext = mainDC;", so "MainDC mainDC = Application.Current.MainWindow.DataContext as MainDC;" will return Null.

    -->why do I need to set the TargetType to Button

    you could refer to document of TargetType,

    http://msdn.microsoft.com/en-us/library/system.windows.style.targettype.aspx

    Remark part.

    As for your last question, animate the Save button, I think this is an new question, and it is not relative to this thread, I suggest you start a new thread, I will discuss it with you on that thread, thank you.

    Best regards,


    Sheldon _Xiao[MSFT]
    MSDN Community Support | Feedback to us
    Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Thursday, August 16, 2012 6:27 AM
    Moderator
  • Thanks Sheldon.  I will open another thread for button animation question.

    One last question on this thread.  Is there another simpler way to accomplish the same functionality (Enable /Animate Save button on MainWindow if any UI element changes in any of the User Control on various tab items) ?  I'm trying to avoid creating MainDC class in every tabitem and setting its DataChanged property to true on each UI element changed event.

    Thanks again for all your help.

    Thursday, August 16, 2012 11:30 AM
  • I think what you did is good method, because if you want to pass data between Mainwindow and UserControl you have to pass an data object as DataContext, then use property changed event to notify your UI.


    Sheldon _Xiao[MSFT]
    MSDN Community Support | Feedback to us
    Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    • Marked as answer by vg_jain Friday, August 17, 2012 3:20 PM
    Friday, August 17, 2012 2:19 AM
    Moderator