简体   繁体   中英

INotifyPropertyChanged not updating UI in DataTemplate, XAML, WPF

I want to display multiple rectangles with some properties, which I store in an Array of Arrays. The XAML for displaying looks like this:

<UserControl x:Class="Project.Views.SomeView">

<UserControl.Resources>
    <DataTemplate x:Key="ObjectRowTemplate">
        <Rectangle Fill="{Binding Path=color, UpdateSourceTrigger=PropertyChanged}" Height="15" Width="30"  StrokeThickness="1px" Stroke="Blue" >
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseDown">
                    <catel:EventToCommand DisableAssociatedObjectOnCannotExecute="False">
                        <catel:EventToCommand.Command>
                            <Binding RelativeSource="{RelativeSource AncestorType={x:Type Canvas}}" Path="DataContext.ObjectClickedCommand"  />
                        </catel:EventToCommand.Command>

                        <catel:EventToCommand.CommandParameter>
                            <Binding Path="."  />
                        </catel:EventToCommand.CommandParameter>
                    </catel:EventToCommand>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Rectangle>   
    </DataTemplate>

    <DataTemplate x:Key="ObjectColumnTemplate">
        <ItemsControl ItemsSource="{Binding ., UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{DynamicResource ObjectRowTemplate}" >
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </DataTemplate>

</UserControl.Resources>
<StackPanel>
    <Canvas Background="Black" Width="{Binding CanvasWidth}" Height="600">
        <Grid>
            <ItemsControl ItemsSource="{Binding Path=BindedProperty, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{DynamicResource ObjectColumnTemplate}"/>
        </Grid>
    </Canvas>
</StackPanel>

there is a StackPanel with a canvas inside which shows the array of array via the two defined DataTemplate s, by creating a Rectangle for each "slot" in the Array .

namespace ViewModels
{
    public class ViewModel : AdvancedViewModelBase
    {
        public CreateGameFieldViewModel()
        {
            ObjectClickedCommand = new Command<Object>(ObjectClicked);

            Objects = new Objects[15][];
            for (int i = 0; i < 15; i++)
            {
                Objects[i] = new Object[15];
                for (int j = 0; j < 15; j++)
                {
                    Objects[i][j] = new Object(someparams);
                }
            }
        }

        private Object _selectedObject;

        private Object[][] _object;

        public Object[][] Objects
        {
            get { return _object; }
            set
            {
                _object = value;
                RaisePropertyChanged(nameof(Objects));
            }
        }

        public int CanvasWidth { get; set; } = 450;

        public Command<OPbject> ObjectClickedCommand { get; set; }

        public void ObjectClicked(Object object)
        {
            if (object != null)
            {
                _selectedObject = object;
            }
        }

        public void OnKeyDown(KeyEventArgs e)
        {
            // Modify _currentObject
            _currentObject.color = Brushes.Blue;

            RaisePropertyChanged(nameof(Objects));
        }
    }
}

The property binding with the viewmodel works fine, the properties are set as defined. By clicking on a Rectangle , the object which is bound to, is now set as _selectedObject and can be modified by the arrow keys, which also works like a charm.

Since I modified one object of the array of arrays, which I am binding to the DataTemplate, I have to notify my UI which I am doing by then line RaisePropertyChanged(nameof(Objects)); (used Nuget: Catel) after the modification.

Unfortunately the UI does not get updated. I checked everything, the object in the array of arrays contains the modifications.

I guess it doesn't work because of the DataTemplate s I used.

Can anyone help me?

I am facing the same issue in UWP when the ObservableCollection of the second level of the parent ObservableCollection won't update UI in DataTemplate though it has implemented INotifyPropertyChanged .

It seems like Routing does not work for the nested ObservableCollection .

I also was not able to force the method myObject.OnPropertyChanged('PropertyName') .

So the solution was folllowing:

  1. Dynamically populate Tag property of the DataTemplate using ObjectToString converter. So the Tag has a string like Author-3 . Where is 3 is the ID of the Author entity and as result we have unique identifiers for all objects created on base of DataTemplate .

  2. Within code just find all objects via Object Type and the Tag name (ie Author-3) and do what you need to change its any property.

  3. And you have working UI!

In my case I will provide chunks of code and you will have whole imagination what's going on.

XAML

 xmlns:myconverters="using:MyProjectFolder.Converters" 


 <UserControl.Resources> 
        <myconverters:LoaderOnlineToColorConverter x:Key="MyLoaderOnlineToColorConverter"/>
       
        
        <muxc:StackLayout  x:Key="UniformGridLayout2" />
        <DataTemplate x:Key="UnitLoaderItemTemplate" x:DataType="x1:UnitLoader">
            <Grid BorderBrush="Gray"  BorderThickness="1" Margin="0,5,0,0" Padding="5,0,5,5" >
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="1*"></ColumnDefinition>
                    <ColumnDefinition Width="6*"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <StackPanel IsDoubleTapEnabled="True" DoubleTapped="Unit_DoubleTapped" Tag="{x:Bind DeviceID}" 
                            Grid.Column="0" Padding="2,0,2,4" Margin="0,5,0,0" Orientation="Horizontal"   
                            BorderBrush="Gray" BorderThickness="1" ToolTipService.ToolTip="{x:Bind FullName}">
                    <FontIcon Tag="{x:Bind Converter={StaticResource MyDeviceIDToControlNameConverter}}" 
                              Foreground="{x:Bind Converter={StaticResource MyLoaderOnlineToColorConverter}}"
                              FontFamily="Segoe MDL2 Assets" Glyph="&#xE701;" VerticalAlignment="Center" Margin="0,0,2,0" />
                    <TextBlock Text="{x:Bind DeviceName}"  VerticalAlignment="Center" /> 
</StackPanel>

NOTE: this line Foreground="{x:Bind Converter={StaticResource MyLoaderOnlineToColorConverter}}" exactly does not work so we have another one

Tag="{x:Bind Converter={StaticResource MyDeviceIDToControlNameConverter}}" 

            

C# The converters

 public class DeviceIDToControlNameConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            var controlName = "Unit-";

            if (value is UnitLoader)
            {
                controlName = "Loader-" + ((UnitLoader)value).DeviceID;
            }
            else if (value is UnitLoader)
            {
                controlName = "Track-" + ((UnitTrack)value).DeviceID;
            }            

            return controlName;
        }
}
     public class LoaderOnlineToColorConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, string language)
            {
                SolidColorBrush color;
    
                if (value is UnitLoader)
                {
                    var isOnline = ((UnitLoader)value).IsOnline;
    
                    if (isOnline)
                    {
                        color = new SolidColorBrush(Colors.YellowGreen);
                    }
                    else
                    {
                        color = new SolidColorBrush(Colors.DarkGray);
                    }
                }
                else
                {
                    color = new SolidColorBrush(Colors.DarkGray);
                }
               
    
                return color;
            } 
    
        }

C# The code to change the property of all objects

string tagName = "Loader-" + unitLoader.DeviceID.ToString();
var fontIcon = FindControl<FontIcon>(this, typeof(FontIcon), tagName);
if (fontIcon != null)
{
  fontIcon.Foreground = unitLoader.IsOnline ? deviceOnline : deviceOffline;
}

C# Method to search all objects via Type and Tag

public static T FindControl<T>(UIElement parent, Type targetType, string ControlName) where T : FrameworkElement
        {

            if (parent == null) return null;

            if (parent.GetType() == targetType && ((T)parent).Tag != null && ((T)parent).Tag.ToString() == ControlName)
            {
                return (T)parent;
            }
            T result = null;
            int count = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < count; i++)
            {
                UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);

                if (FindControl<T>(child, targetType, ControlName) != null)
                {
                    result = FindControl<T>(child, targetType, ControlName);
                    break;
                }
            }
            return result;
        }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM