Ошибка привязки данных в UserControl - PullRequest
0 голосов
/ 13 февраля 2020

Я пытался создать небольшое приложение, расположенное поверх интерфейса POS и позволяющее пользователю получать доступ к некоторой информации, недоступной через стандартное программное обеспечение POS внешними пользовательскими приложениями C#. Одним из них был поиск квитанции, который после демонстрации попросили расширить, чтобы также проверить детали заказа онлайн.

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

Вот базовый класс, унаследованный моделью:

using System;
using System.ComponentModel;
using System.Diagnostics;

namespace RGLibrary
{
    public abstract class Observable_Object : INotifyPropertyChanged

    {
        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);

            if (this.PropertyChanged != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                this.PropertyChanged(this, e);
            }
        }

        #endregion // INotifyPropertyChanged Members

        #region Debugging Aides

        /// <summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This
        /// method does not exist in a Release build.
        /// </summary>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public virtual void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real,
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;

                if (this.ThrowOnInvalidPropertyName)
                    throw new Exception(msg);
                else
                    Debug.Fail(msg);
            }
        }

        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might
        /// override this property's getter to return true.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        #endregion // Debugging Aides
    }
}

Унаследованный класс от виртуальных машин:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace RGLibrary
{
    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
        {
            if (Equals(member, val))
                return false;

            member = val;
            OnPropertyChanged(propertyName);
            return true;
        }

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

    }
}

Вот основной класс VM:

using RGLibrary;
using Store_Launcher.Model;

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace Store_Launcher.ViewModel
{
    public class ReceiptLookupVM: BindableBase
    {
        private string _ReceiptNumber;
        private ReceiptLookupModel _ReceiptDetails;
        public ReceiptLookupModel ReceiptDetails
        {
            get { return _ReceiptDetails; }
            set { _ReceiptDetails = value; OnPropertyChanged("ReceiptDetails"); }
        }
        public RGDBConnect rms = new RGDBConnect("");
        public string ReceiptNumber
        {
            get { return _ReceiptNumber; }
            set { _ReceiptNumber = value; OnPropertyChanged("ReceiptNumber"); }
        }

        private OnlineOrderDetailsVM orderDetailsVM = new OnlineOrderDetailsVM();
        private ReceiptDetailsVM receiptDetailsVM = new ReceiptDetailsVM();

        private BindableBase _CurrentMode;
        public BindableBase CurrentMode
        {
            get { return _CurrentMode; }
            set { SetProperty(ref _CurrentMode, value); }
        }



        public ReceiptLookupVM()
        {
            ReceiptDetails = new ReceiptLookupModel();
            ReceiptNumber = "";
            if (System.Diagnostics.Debugger.IsAttached)
                rms = new RGDBConnect(ConfigurationManager.AppSettings["rmstest"]);
            else
                rms = new RGDBConnect(ConfigurationManager.AppSettings["rms"]);
            CheckCommand = new MyICommand<string>(OnCheck);
            CurrentMode = receiptDetailsVM;
        }
        public MyICommand<string> CheckCommand { get; private set; }
        private void OnCheck(string command)
        {
            ReceiptDetails.Receipt = _ReceiptNumber;

            string query = "rg_launcher_receiptref_ext '" + ReceiptDetails.Receipt + "'";
            try
            {
                DataTable results = rms.ExecuteSelect(query);
                if (results.Rows.Count == 1)
                {
                    DataRow resultRow = results.Rows[0];
                    if (resultRow["tran_type"].ToString() == "SALE")
                    {
                        ReceiptDetails.SaleCode = resultRow["sale_code"].ToString();
                        ReceiptDetails.Customer = resultRow["name"].ToString();
                        ReceiptDetails.CustomerID = resultRow["customer_id"].ToString();
                        ReceiptDetails.Items = resultRow["units"].ToString();
                        ReceiptDetails.Value = resultRow["value"].ToString();
                        ReceiptDetails.Stylist = resultRow["stylist"].ToString();
                        ReceiptDetails.TransactionType = ReceiptLookupModel.TransType.RetailOrder;
                        receiptDetailsVM.UpdateDetails(ReceiptDetails);
                        CurrentMode = receiptDetailsVM;
                    }
                    else if (resultRow["tran_type"].ToString() == "WEB ORDER")
                    {
                        ReceiptDetails.SaleCode = resultRow["sale_code"].ToString();
                        ReceiptDetails.ReceiptNumber = resultRow["receipt_ref"].ToString();
                        ReceiptDetails.Customer = resultRow["name"].ToString();
                        ReceiptDetails.CustomerID = resultRow["customer_id"].ToString();
                        ReceiptDetails.Items = resultRow["units"].ToString();
                        ReceiptDetails.Value = resultRow["value"].ToString();
                        ReceiptDetails.TransactionType = ReceiptLookupModel.TransType.OnlineOrder;
                        orderDetailsVM.UpdateDetails(ReceiptDetails);
                        CurrentMode = orderDetailsVM;
                    }
                    else
                    {
                        MessageBox.Show(
                            "Unable to determine the transaction type for this number. Please contact IT for assistance",
                            "Receipt Lookup: Unknown order number",
                            MessageBoxButton.OK,
                            MessageBoxImage.Warning);
                        ReceiptDetails = new ReceiptLookupModel();
                        receiptDetailsVM.UpdateDetails(ReceiptDetails);
                        CurrentMode = receiptDetailsVM;
                    }
                }
                else if (results.Rows.Count == 0)
                {
                    MessageBox.Show(
                        "Unable to find this receipt number in the system. Please make sure that the receipt number has been entered correctly.",
                        "Receipt Lookup: Unable to find sale",
                        MessageBoxButton.OK,
                        MessageBoxImage.Exclamation);
                    ReceiptDetails = new ReceiptLookupModel();
                    receiptDetailsVM.UpdateDetails(ReceiptDetails);
                    CurrentMode = receiptDetailsVM;
                }
                else
                {
                    MessageBox.Show(
                        "An error has occured and the system is unable to properly locate this receipt number in the system. Please contact IT for assistance",
                        "Receipt Lookup: Unable to find sale",
                        MessageBoxButton.OK,
                        MessageBoxImage.Warning);
                    ReceiptDetails = new ReceiptLookupModel();
                    receiptDetailsVM.UpdateDetails(ReceiptDetails);
                    CurrentMode = receiptDetailsVM;
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(
                    e.Message,
                    "Receipt Lookup: An error has occurred",
                    MessageBoxButton.OK,
                    MessageBoxImage.Warning);
                MessageBox.Show(
                    "An error has occured and the system is unable to properly locate this receipt number in the system. Please check to make sure your computer is currently connected to the internet. Contact IT for further assistance",
                    "Receipt Lookup: Unable to lookup receipt number",
                    MessageBoxButton.OK,
                    MessageBoxImage.Exclamation);
                ReceiptDetails = new ReceiptLookupModel();
                receiptDetailsVM.UpdateDetails(ReceiptDetails);
                CurrentMode = receiptDetailsVM;
            }
        }
    }
}

Вот соответствующее представление:

<Window x:Class="Store_Launcher.Views.ReceiptLookupView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Store_Launcher.Views"
        xmlns:viewmodel="clr-namespace:Store_Launcher.ViewModel"
        mc:Ignorable="d"
        Title="Rodd &amp; Gunn Launcher: Receipt Lookup" Height="195" Width="450"
        ShowInTaskbar="True" ResizeMode="NoResize" Topmost="True" >
    <Window.DataContext>
        <viewmodel:ReceiptLookupVM/>
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate DataType="{x:Type viewmodel:OnlineOrderDetailsVM}">
            <local:OnlineOrderDetailsView/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type viewmodel:ReceiptDetailsVM}">
            <local:ReceiptDetailsView/>
        </DataTemplate>
    </Window.Resources>
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Close"
                        Executed="CloseCommandHandler"/>
    </Window.CommandBindings>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="AUTO"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="AUTO"/>
            <RowDefinition Height="AUTO"/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0"
                    Grid.Column="0"
                    Orientation="Horizontal"
                    Margin="5"
                    VerticalAlignment="Center">
            <TextBlock Text="Receipt Number: "/>
            <TextBox Width="100"
                     Text="{Binding ReceiptNumber, Mode=TwoWay}"/>
        </StackPanel>

        <!-- New User Control XAML to switch between brick and mortar, and online order modes -->

        <UserControl
            Margin="5"
            Height="115"
            Width="230"
            Grid.Column="1">
            <ContentControl Content="{Binding CurrentMode}"/>
        </UserControl>

        <!-- Original Grid XAML -->

        <!--<Grid Grid.Row="0"
              Grid.Column="1"
              Margin="5"
              DataContext="{Binding ReceiptDetails}">
            <Grid.RowDefinitions>
                <RowDefinition Height="AUTO"/>
                <RowDefinition Height="AUTO"/>
                <RowDefinition Height="AUTO"/>
                <RowDefinition Height="AUTO"/>
                <RowDefinition Height="AUTO"/>
                <RowDefinition Height="AUTO"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="AUTO"/>
                <ColumnDefinition Width="AUTO"/>
            </Grid.ColumnDefinitions>
            <TextBlock
                Grid.Row="0"
                Grid.Column="0"
                Text="Sale Code: "/>
            <TextBlock
                Grid.Row="0"
                Grid.Column="1"
                Text="{Binding SaleCode}"/>
            <TextBlock
                Grid.Row="1"
                Grid.Column="0"
                Text="Customer ID:  "/>
            <TextBlock
                Grid.Row="1"
                Grid.Column="1"
                Text="{Binding CustomerID}"/>
            <TextBlock
                Grid.Row="2"
                Grid.Column="0"
                Text="Customer: "/>
            <TextBlock
                Grid.Row="2"
                Grid.Column="1"
                Text="{Binding Customer}"/>
            <TextBlock
                Grid.Row="3"
                Grid.Column="0"
                Text="Items: "/>
            <TextBlock
                Grid.Row="3"
                Grid.Column="1"
                Text="{Binding Items}"/>
            <TextBlock
                Grid.Row="4"
                Grid.Column="0"
                Text="Value: "/>
            <TextBlock
                Grid.Row="4"
                Grid.Column="1"
                Text="{Binding Value}"/>
            <TextBlock
                Grid.Row="5"
                Grid.Column="0"
                Text="Stylist: "/>
            <TextBlock
                Grid.Row="5"
                Grid.Column="1"
                Text="{Binding Stylist}"/>
        </Grid>-->

        <StackPanel
            Grid.Column="0"
            Grid.Row="1"
            Grid.ColumnSpan="2"
            Orientation="Horizontal"
            HorizontalAlignment="Center">
            <Button
                Width="100"
                Height="20"
                Margin="5"
                Content="CHECK"
                Command="{Binding CheckCommand}"/>
            <Button
                Width="100"
                Height="20"
                Margin="5"
                Content="CLOSE"
                Command="ApplicationCommands.Close"/>
        </StackPanel>


    </Grid>
</Window>

Здесь модель с деталями заказа:

using RGLibrary;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Store_Launcher.Model
{
    public class ReceiptLookupModel: Observable_Object
    {
        private string _Receipt;
        private string _SaleCode;
        private string _ReceiptNumber;
        private string _CustomerID;
        private string _Customer;
        private string _Items;
        private string _Value;
        private string _Stylist;
        private TransType? _TransactionType;
        public string Receipt
        {
            get { return _Receipt = (_Receipt ?? ""); }
            set { _Receipt = value; OnPropertyChanged("Receipt"); }
        }
        public string SaleCode
        {
            get { return _SaleCode = (_SaleCode ?? ""); }
            set { _SaleCode = value; OnPropertyChanged("SaleCode"); }
        }
        public string ReceiptNumber
        {
            get { return _ReceiptNumber = (_ReceiptNumber ?? ""); }
            set { _ReceiptNumber = value; OnPropertyChanged("ReceiptNumber"); }
        }
        public string CustomerID
        {
            get { return _CustomerID = (_CustomerID ?? ""); }
            set { _CustomerID = value; OnPropertyChanged("CustomerID"); }
        }
        public string Customer
        {
            get { return _Customer = (_Customer ?? ""); }
            set { _Customer = value; OnPropertyChanged("Customer"); }
        }
        public string Items
        {
            get { return _Items = (_Items ?? "0"); }
            set { _Items = value; OnPropertyChanged("Items"); }
        }
        public string Value
        {
            get { return _Value = (_Value ?? "$0.00"); }
            set { _Value = value; OnPropertyChanged("Value"); }
        }
        public string Stylist
        {
            get { return _Stylist = (_Stylist ?? ""); }
            set { _Stylist = value; OnPropertyChanged("Stylist"); }
        }
        public TransType? TransactionType
        {
            get { return _TransactionType = (_TransactionType ?? TransType.None); }
            set { _TransactionType = value; OnPropertyChanged("TransactionType"); }
        }


        public enum TransType
        {
            OnlineOrder,
            RetailOrder,
            None
        }
    }
}

модель просмотра онлайн-заказов:

using RGLibrary;
using Store_Launcher.Model;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace Store_Launcher.ViewModel
{
    public class OnlineOrderDetailsVM: BindableBase
    {
        private ReceiptLookupModel _OrderDetails;
        public ReceiptLookupModel OrderDetails
        {
            get { return _OrderDetails; }
            set { _OrderDetails = value; OnPropertyChanged("OrderDetails"); }
        }
        public OnlineOrderDetailsVM()
        {
            OrderDetails = new ReceiptLookupModel();
        }

        public void UpdateDetails(ReceiptLookupModel SQLData)
        {
            ReceiptLookupModel _data = new ReceiptLookupModel();
            _data.Customer = SQLData.Customer;
            _data.CustomerID = SQLData.CustomerID;
            _data.Items = SQLData.Items;
            _data.Receipt = SQLData.Receipt;
            _data.ReceiptNumber = SQLData.Receipt;
            _data.SaleCode = SQLData.SaleCode;
            _data.Stylist = SQLData.Stylist;
            _data.TransactionType = SQLData.TransactionType;
            _data.Value = SQLData.Value;
            OrderDetails = _data;

        }
    }
}

Представление с деталями заказа:

<UserControl x:Name="OnlineOrderDetailsUC"
             x:Class="Store_Launcher.Views.OnlineOrderDetailsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Store_Launcher.Views"  
             xmlns:viewmodel="clr-namespace:Store_Launcher.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="115" d:DesignWidth="230">
    <UserControl.DataContext>
        <viewmodel:OnlineOrderDetailsVM/>
    </UserControl.DataContext>

    <Grid Grid.Row="0"
          Grid.Column="1"
          Margin="5"
          DataContext="{Binding OrderDetails, NotifyOnSourceUpdated=True}">
        <Grid.RowDefinitions>
            <RowDefinition Height="AUTO"/>
            <RowDefinition Height="AUTO"/>
            <RowDefinition Height="AUTO"/>
            <RowDefinition Height="AUTO"/>
            <RowDefinition Height="AUTO"/>
            <RowDefinition Height="AUTO"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="AUTO"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock
                Grid.Row="0"
                Grid.Column="0"
                Text="Sale Code: "/>
        <TextBlock
                Grid.Row="0"
                Grid.Column="1"
                Text="{Binding SaleCode}"/>
        <TextBlock
                Grid.Row="1"
                Grid.Column="0"
                Text="Receipt No:  "/>
        <TextBlock
                Grid.Row="1"
                Grid.Column="1"
                Text="{Binding ReceiptNumber}"/>
        <TextBlock
                Grid.Row="2"
                Grid.Column="0"
                Text="Customer ID:  "/>
        <TextBlock
                Grid.Row="2"
                Grid.Column="1"
                Text="{Binding CustomerID}"/>
        <TextBlock
                Grid.Row="3"
                Grid.Column="0"
                Text="Customer: "/>
        <TextBlock
                Grid.Row="3"
                Grid.Column="1"
                Text="{Binding Customer}"/>
        <TextBlock
                Grid.Row="4"
                Grid.Column="0"
                Text="Items: "/>
        <TextBlock
                Grid.Row="4"
                Grid.Column="1"
                Text="{Binding Items}"/>
        <TextBlock
                Grid.Row="5"
                Grid.Column="0"
                Text="Value: "/>
        <TextBlock
                Grid.Row="5"
                Grid.Column="1"
                Text="{Binding Value}"/>
    </Grid>
</UserControl>

Что меня смущает заключается в том, что в представлении receiveLookup, когда я использую закомментированную сетку вместо секции usercontrol, которая правильно привязывается к объекту модели в viewModel ReceiveLookup, но привязка usercontrol, похоже, не работает.

Я пытался решить эту проблему несколькими способами. Первоначально вместо использования метода внутри view-модели orderdetails для установки значения объекта модели я просто устанавливал его так же, как тот, который был сгенерирован в view-модели receivelookup.

Кроме того, я попытался использовать diag: PresentationTraceSources .TraceLevel = Высокая привязка для диагностики, если есть ошибка привязки, основанная на других вопросах, которые были заданы ранее. Я думаю, что это привязка к чему-то, потому что нет ошибки при сбое привязки, и он перечисляет ha sh для объекта, к которому привязывается.

В дополнение к этому я попытался удалить текст данных как из пользовательского элемента управления представления заказа, а также из текста данных сетки данных, так и указать его в пути привязки, но это также не изменило никаких результатов.

Еще один вопрос был дан совет, чтобы добавить x: name в их представления, поэтому я сделал то же самое без каких-либо изменений. Я также попытался добавить NotifyOnSourceUpdate = True к привязке каждого текстового блока и Mode = TwoWay, но ни одна, ни комбинация обоих не помогла.

Я использовал аналогичный подраздел usercontrol основного представления в Несколько предыдущих приложений на данный момент, так что я действительно застрял в том, почему это не работает в этом случае. Я попытался добавить весь соответствующий код, но если вам нужно, чтобы я перечислил что-нибудь еще, пожалуйста, дайте мне знать в своем комментарии.

1 Ответ

0 голосов
/ 13 февраля 2020

Если у вас есть UserControl в DataTemplate, например

<DataTemplate DataType="{x:Type viewmodel:OnlineOrderDetailsVM}">
    <local:OnlineOrderDetailsView/>
</DataTemplate>

, UserControl должен наследовать свой DataContext от своего родительского объекта, который здесь является ContentControl или ContentPresenter. DataContext содержит экземпляр типа, определенного свойством DataTemplate DataType (то есть Content ContentControl или ContentPresenter).

Это наследование значения свойства работает только в том случае, если вы это делаете not explicity установить свойство, как в

<UserControl.DataContext>
    <viewmodel:OnlineOrderDetailsVM/>
</UserControl.DataContext>

Это значение DataContext имеет более высокий приоритет, чем унаследованное значение. В результате UserControl в DataTemplate всегда привязывается к своему частному объекту DataContext, но не к объекту, предоставленному шаблоном.

Так что просто удалите назначение DataContext из XAML UserControl. Элементы управления никогда не должны явно устанавливать свой собственный DataContext, поэтому никогда не должны иметь объекты модели частного представления.

...