двусторонняя привязка работает для одного элемента, но не для другого

В настоящее время я создаю uwp, где пользователи могут добавлять пользовательские элементы xaml в графический интерфейс и устанавливать определенные свойства. Также можно настроить общие свойства страницы. Для этого на странице дизайна есть панель свойств. На этой панели свойств я хочу либо отображать изменяемые свойства из выбранного xaml-элемента, либо (если пользователь выбирает один конкретный элемент) он должен отображать общие свойства. Я хочу использовать двустороннюю привязку данных, чтобы значения динамически отображались на панели свойств, а также чтобы изменения, внесенные в панель свойств, передавались источнику.

У меня не было проблем с реализацией этой двусторонней привязки для отображения деталей выбранного xaml-элемента, но для общих свойств привязка, похоже, является только односторонней. Когда общее свойство изменяется в коде, оно передается целевому объекту, но когда данные изменяются в графическом интерфейсе, он не передает данные источнику.

Итак, я поискал в Интернете и попытался изменить свой код, добавив свойства зависимостей, изменив private ‹--› public, реализовав INotifyPropertyChanged различными способами и т. Д. К сожалению, ничего из этого, похоже, не работает.

Ниже я приведу как рабочий, так и нефункциональный код C # / xaml. Если кто-нибудь сможет определить проблему, я был бы очень благодарен.

Общий xaml для целевого элемента управления на странице дизайна

<StackPanel x:Name="PropsPanel" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" Orientation="Vertical" Background="GhostWhite" BorderThickness="1" BorderBrush="GhostWhite" Margin="4">
         <TextBlock Text="Properties" Margin="4,4,0,40" Foreground="Black" FontWeight="Bold"/>
         <ContentControl Name="PropertiesPanel" Foreground="Black">
         </ContentControl>
</StackPanel>

Datatemplate xaml с рабочей двусторонней привязкой для свойств xaml-element

<DataTemplate x:Key="TimerElementTemplate">
    <Grid HorizontalAlignment="Stretch">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Margin="2" Text="Timer" FontWeight="SemiBold"/>
        <TextBlock Grid.Row="1" Grid.Column="0" Margin="2" Text="Column "/>
        <TextBlock Grid.Row="1" Grid.Column="1" Margin="2" Text="{Binding Path=ShowColumn, Mode=OneWay}" />
        <TextBlock Grid.Row="2" Grid.Column="0" Margin="2" Text="Row " />
        <TextBlock Grid.Row="2" Grid.Column="1" Margin="2" Text="{Binding Path=ShowRow, Mode=OneWay}"/>
        <TextBlock Grid.Row="3" Grid.Column="0" Margin="2" Text="Name "/>
        <TextBox Grid.Row="3" Grid.Column="1" BorderThickness="1" Margin="2" Text="{Binding Path=ElementName, Mode=TwoWay}" />
        <TextBlock Grid.Row="4" Grid.Column="0" Margin="2" Text="Label " />
        <TextBox Grid.Row="4" Grid.Column="1" Margin="2" BorderThickness="1" Text="{Binding Path=Label, Mode=TwoWay}" />
    </Grid>
</DataTemplate>

Рабочий код для настройки контекста данных

private void GOnPointerReleased(object sender, PointerRoutedEventArgs pointerRoutedEventArgs)
    {
        if (_selectedGuiElement != null)
        {
            _selectedGuiElement.BorderThickness = new Thickness(0);
        }
        PropertiesPanel.DataContext = sender;
        PropertiesPanel.ContentTemplate = Resources[sender.GetType().Name + "Template"] as DataTemplate;
        var element = sender as GuiElement;
        element.BorderBrush = new SolidColorBrush(Colors.Crimson);
        element.BorderThickness = new Thickness(2.0);
        _selectedGuiElement = element;
        
    }

Элементы с отображаемыми свойствами - это элементы графического интерфейса, которые наследуются от Xaml-элемента управления Grid. Отображаемые мной свойства отмечены атрибутом GUIElementProperty. Код для элемента графического интерфейса приведен ниже:

public class GuiElement : Grid
{
    protected string _elementType;

    public GuiElement(bool design)
    {
        DesignState = design;
        SetElementType();
        AddContent();
        if(DesignState)
        AllowDrop = true;
    }

    //overridable method to set Type and Type related properties
    public virtual void SetElementType()
    {
        _elementType = "";
        GuiBackground = new SolidColorBrush(Colors.DarkGray);
    }
    //overridable method to set lay-out content for each type
    public virtual void AddContent()
    {
        Background = GuiBackground;
        BorderThickness = new Thickness(1);
        BorderBrush = new SolidColorBrush(Colors.GhostWhite);
        SetColumnSpan(this, ColumnSpan);
        SetRowSpan(this, RowSpan);
    }


    // shortcuts for Grid.Column and Grid.Row properties and other general properties
    [GuiElementProperty("lbl", "Column", 2)]
    public int Column
    {
        get { return GetColumn(this); }
        set { SetColumn(this, value); }
    }

    public string ShowColumn
    {
        get { return Column.ToString(); }
    }

    [GuiElementProperty("lbl", "Row", 1)]
    public int Row
    {
        get { return GetRow(this); }
        set { SetRow(this, value); }
    }

    public string ShowRow
    {
        get { return Row.ToString(); }
    }

    public int ColumnSpan { get; set; } = 1;

    public int RowSpan { get; set; } = 1;

    public bool DesignState { get; set; }

    public SolidColorBrush GuiBackground { get; set; }

    public int Id { get; set; }

    [GuiElementProperty("lbl", "Type", 0)]
    public string ElementType { get { return _elementType; } }
}

}

Пока рабочий код ... теперь проблемная часть

Datatemplate xaml для отображения общих свойств с дисфункциональной двусторонней привязкой

<DataTemplate x:Key="StartElementTemplate">
    <Grid HorizontalAlignment="Stretch">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Margin="2" Text="Session" FontWeight="SemiBold"/>
        <!--<Button Foreground="GhostWhite" Grid.Row="0" Grid.Column="1" Content="X" Click="ButtonDeleteFromProperties_OnClick"></Button>-->
        <TextBlock Grid.Row="1" Grid.Column="0" Margin="2" Text="Name "/>
        <TextBlock Grid.Row="1" Grid.Column="1" Margin="2" Text="{Binding Path=DesignName, Mode=OneWay}"/>
        <TextBlock Grid.Row="2" Grid.Column="0" Margin="2" Text="Time Limit (true/false) "/>
        <TextBox Grid.Row="2" Grid.Column="1" Margin="2" BorderThickness="1" Text="{Binding Path=LimitedTime, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBlock Grid.Row="3" Grid.Column="0" Margin="2" Text="Max. duration (hh:mm:ss) "/>
        <TextBox Grid.Row="3" Grid.Column="1" BorderThickness="1" Margin="2" Text="{Binding Path=Timelimit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</DataTemplate>

код класса с общими свойствами

 public class SessionProps : INotifyPropertyChanged   
{   private string _designname;
   private bool _limitedtime;
   private TimeSpan _timelimit;

   [GuiElementProperty("txtbx", "Design name", 0)]
    public string DesignName
    {
        get { return _designname; }
        set
        {
            _designname = value;
            NotifyPropertyChanged();
        }
    }

    [GuiElementProperty("bool", "Time limit", 1)]
   public bool LimitedTime
    {
        get { return _limitedtime; }
        set
        {
            _limitedtime = value;
            NotifyPropertyChanged();
        }
    }

    [GuiElementProperty("txtbx", "Max. Duration (hh:mm:ss)", 2)]
   public TimeSpan Timelimit
    {
       get { return _timelimit; }
       set
       {
           _timelimit = value;
            NotifyPropertyChanged();
        } 
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Создание экземпляра класса общих свойств

 public sealed partial class DesignPage : Page
{
    private SessionProps _props = new SessionProps() {DesignName = "", LimitedTime = false, Timelimit = TimeSpan.Zero};

управление привязкой кода к этому источнику

 private void SOnPointerReleased(object sender, PointerRoutedEventArgs e)
    {
        if (_selectedGuiElement != null)
        {
            _selectedGuiElement.BorderThickness = new Thickness(0);
        }
        PropsPanel.DataContext = _props;
        PropertiesPanel.ContentTemplate = Resources[sender.GetType().Name + "Template"] as DataTemplate;
        var element = sender as GuiElement;
        element.BorderBrush = new SolidColorBrush(Colors.Crimson);
        element.BorderThickness = new Thickness(2.0);
        _selectedGuiElement = element;
    }

Я надеюсь, что здесь есть что-то маленькое, но я буду благодарен за любую помощь! Надеюсь, я предоставил достаточно информации, но если что-то еще неясно, спросите.


person ljanssen    schedule 21.03.2016    source источник


Ответы (1)


Причина в том, что LimitedTime - это bool тип, Timelimit - это тип TimeSpan, а текст в TextBox - это string тип. Когда вы редактируете TextBox, он получит сообщение об ошибке: конвертеру не удалось преобразовать значение типа Windows.Foundation.String в тип Boolean;

Вы можете создать класс, который позволяет преобразовывать формат ваших данных между источником и целевым объектом, унаследовав его от IValueConverter.

Вы всегда должны реализовывать Convert с функциональной реализацией, но это довольно часто для реализации ConvertBack, чтобы он сообщал о нереализованном исключении. Вам нужен только метод ConvertBack в вашем конвертере, если вы используете конвертер для двусторонних привязок или используете XAML для сериализации.

Для получения дополнительной информации см. _ 7_.

Например:

public class BoolFormatter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return value.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        if (value.ToString() == "true" || value.ToString() == "True")
        {
            return true;
        }
        else if (value.ToString() == "false" || value.ToString() == "False")
        {
            return false;
        }
        else
        {
            return "";
        }
    }
}

Создать TimeSpanFormatter класс:

public class TimeSpanFormatter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value != null)
        {
            return value.ToString();
        }
        else
        {
            return null;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        TimeSpan span;

        if (TimeSpan.TryParse(value.ToString(), out span))
        {
            return span;
        }
        else
        {
            return "";
        }
    }
}

В XAML:

<Page.Resources>
    <local:BoolFormatter x:Key="BoolConverter" />
    <local:TimeSpanFormatter x:Key="TimeSpanConverter" />
    <DataTemplate>
        <Grid HorizontalAlignment="Stretch">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0" Margin="2" Text="Session" FontWeight="SemiBold" />
            <!--<Button Foreground="GhostWhite" Grid.Row="0" Grid.Column="1" Content="X" Click="ButtonDeleteFromProperties_OnClick"></Button>-->
            <TextBlock Grid.Row="1" Grid.Column="0" Margin="2" Text="Name " />
            <TextBlock Grid.Row="1" Grid.Column="1" Margin="2" Text="{Binding Path=DesignName, Mode=TwoWay}" />
            <TextBlock Grid.Row="2" Grid.Column="0" Margin="2" Text="Time Limit (true/false) " />
            <TextBox Grid.Row="2" Grid.Column="1" Margin="2" BorderThickness="1" Text="{Binding Path=LimitedTime, Mode=TwoWay,Converter={StaticResource BoolConverter}}" />
            <TextBlock Grid.Row="3" Grid.Column="0" Margin="2" Text="Max. duration (hh:mm:ss) " />
            <TextBox Grid.Row="3" Grid.Column="1" BorderThickness="1" Margin="2" Text="{Binding Path=Timelimit, Mode=TwoWay,Converter={StaticResource TimeSpanConverter}}" />
        </Grid>
    </DataTemplate>
</Page.Resources>
person Jayden    schedule 22.03.2016
comment
Спасибо, это действительно была проблема. Теперь bool привязывается к флажку, и это работает без конвертера. Для промежутка времени я использовал конвертер. Я также читал кое-что об использовании резервного значения в конвертере. Я нашел, как установить это значение в xaml, но есть ли у вас пример того, как использовать это в коде конвертера? - person ljanssen; 23.03.2016
comment
Существует случай, связанный с резервным значением, см. это. - person Jayden; 24.03.2016