public OperationItemViewModel(ReactiveProperty<ObservablePhotonPeer> peer, ReactiveProperty<string> log, OperationInfo info)
        {
            Info = info;
            ParameterItems = info.FlatternedParameters
                .Select(x =>
                {
                    var piv = new ParameterItemViewModel
                    {
                        Name = x.Name,
                        TypeName = x.TypeName,
                        Comment = x.Comment,
                        IsNeedTemplate = x.IsNeedTemplate,
                        InsertButtonVisibility = x.IsNeedTemplate ? Visibility.Visible : Visibility.Hidden,
                        Template = (x.IsNeedTemplate) ? x.Template : null
                    };
                    if (x.DefaultValue != null) piv.ParameterValue.Value = x.DefaultValue.ToString();
                    return piv;
                })
                .ToArray();

            CopyCommand = new ReactiveCommand(Observable.Return(ParameterItems.Any()));
            CopyCommand.Subscribe(_ =>
            {
                var data = new ClipboardData
                {
                    HubName = Info.Hub.HubName,
                    OperationName = Info.OperationName,
                    Data = ParameterItems.Select(x => x.ParameterValue.Value).ToArray()
                };
                var value = JsonConvert.SerializeObject(data);

                Clipboard.SetText(value, TextDataFormat.UnicodeText);
            });

            if (!ParameterItems.Any())
            {
                PasteCommand = new ReactiveCommand(Observable.Return(false));
            }
            else
            {
                PasteCommand = ClipboardMonitor.CurrentClipboard
                    .Select(text =>
                    {
                        try
                        {
                            if (text.Contains(nameof(ClipboardData.HubName)) && text.Contains(nameof(ClipboardData.OperationName)))
                            {
                                var cd = JsonConvert.DeserializeObject<ClipboardData>(text);
                                if (cd.HubName == Info.Hub.HubName && cd.OperationName == Info.OperationName) return true;
                            }

                            return false;
                        }
                        catch
                        {
                            return false;
                        }
                    })
                    .ToReactiveCommand(initialValue: false);
                PasteCommand.Subscribe(_ =>
                {
                    try
                    {
                        if (Clipboard.ContainsText(TextDataFormat.UnicodeText))
                        {
                            var text = Clipboard.GetText();

                            var cd = JsonConvert.DeserializeObject<ClipboardData>(text);

                            var index = 0;
                            foreach (var item in cd.Data)
                            {
                                ParameterItems[index].ParameterValue.Value = item;
                                index++;
                            }
                        }
                    }
                    catch { }
                });
            }

            SendCommand = new ReactiveCommand();
            SendCommand.Subscribe(async _ =>
            {
                try
                {
                    byte opCode = Info.OperationId;
                    var parameter = new System.Collections.Generic.Dictionary<byte, object>();
                    parameter.Add(ReservedParameterNo.RequestHubId, Info.Hub.HubId);

                    // grouping
                    var grouping = ParameterItems.GroupBy(x =>
                    {
                        var split = x.Name.Split('.');
                        return split[0];
                    });

                    var index = 0;
                    foreach (var item in grouping)
                    {
                        if (item.Count() == 1)
                        {
                            var p = item.First();
                            parameter.Add((byte)index, JsonPhotonSerializer.Serialize(p.TypeName, p.ParameterValue.Value));
                        }
                        else
                        {
                            // Object
                            var p = BuildJson(item);
                            parameter.Add((byte)index, Encoding.UTF8.GetBytes(p)); // send byte[]
                        }
                        index++;
                    }

                    var response = await peer.Value.OpCustomAsync(opCode, parameter, true);
                    var result = response[ReservedParameterNo.ResponseId];

                    var deserialized = JsonPhotonSerializer.Deserialize(result);
                    log.Value += "+ " + Info.Hub.HubName + "/" + Info.OperationName + ":" + deserialized + "\r\n";
                }
                catch (Exception ex)
                {
                    log.Value += "Send Error:" + ex.ToString() + "\r\n";
                }
            });
        }
        public OperationItemViewModel(ReactiveProperty <ObservablePhotonPeer> peer, ReactiveProperty <string> log, OperationInfo info)
        {
            Info           = info;
            ParameterItems = info.FlatternedParameters
                             .Select(x =>
            {
                var piv = new ParameterItemViewModel
                {
                    Name                   = x.Name,
                    TypeName               = x.TypeName,
                    Comment                = x.Comment,
                    IsNeedTemplate         = x.IsNeedTemplate,
                    InsertButtonVisibility = x.IsNeedTemplate ? Visibility.Visible : Visibility.Hidden,
                    Template               = (x.IsNeedTemplate) ? x.Template : null
                };
                if (x.DefaultValue != null)
                {
                    piv.ParameterValue.Value = x.DefaultValue.ToString();
                }
                return(piv);
            })
                             .ToArray();

            CopyCommand = new ReactiveCommand(Observable.Return(ParameterItems.Any()));
            CopyCommand.Subscribe(_ =>
            {
                var data = new ClipboardData
                {
                    HubName       = Info.Hub.HubName,
                    OperationName = Info.OperationName,
                    Data          = ParameterItems.Select(x => x.ParameterValue.Value).ToArray()
                };
                var value = JsonConvert.SerializeObject(data);

                Clipboard.SetText(value, TextDataFormat.UnicodeText);
            });

            if (!ParameterItems.Any())
            {
                PasteCommand = new ReactiveCommand(Observable.Return(false));
            }
            else
            {
                PasteCommand = ClipboardMonitor.CurrentClipboard
                               .Select(text =>
                {
                    try
                    {
                        if (text.Contains(nameof(ClipboardData.HubName)) && text.Contains(nameof(ClipboardData.OperationName)))
                        {
                            var cd = JsonConvert.DeserializeObject <ClipboardData>(text);
                            if (cd.HubName == Info.Hub.HubName && cd.OperationName == Info.OperationName)
                            {
                                return(true);
                            }
                        }

                        return(false);
                    }
                    catch
                    {
                        return(false);
                    }
                })
                               .ToReactiveCommand(initialValue: false);
                PasteCommand.Subscribe(_ =>
                {
                    try
                    {
                        if (Clipboard.ContainsText(TextDataFormat.UnicodeText))
                        {
                            var text = Clipboard.GetText();

                            var cd = JsonConvert.DeserializeObject <ClipboardData>(text);

                            var index = 0;
                            foreach (var item in cd.Data)
                            {
                                ParameterItems[index].ParameterValue.Value = item;
                                index++;
                            }
                        }
                    }
                    catch { }
                });
            }

            SendCommand = new ReactiveCommand();
            SendCommand.Subscribe(async _ =>
            {
                try
                {
                    byte opCode   = Info.OperationId;
                    var parameter = new System.Collections.Generic.Dictionary <byte, object>();
                    parameter.Add(ReservedParameterNo.RequestHubId, Info.Hub.HubId);

                    // grouping
                    var grouping = ParameterItems.GroupBy(x =>
                    {
                        var split = x.Name.Split('.');
                        return(split[0]);
                    });

                    var index = 0;
                    foreach (var item in grouping)
                    {
                        if (item.Count() == 1)
                        {
                            var p = item.First();
                            parameter.Add((byte)index, JsonPhotonSerializer.Serialize(p.TypeName, p.ParameterValue.Value));
                        }
                        else
                        {
                            // Object
                            var p = BuildJson(item);
                            parameter.Add((byte)index, Encoding.UTF8.GetBytes(p)); // send byte[]
                        }
                        index++;
                    }

                    var response = await peer.Value.OpCustomAsync(opCode, parameter, true);
                    var result   = response[ReservedParameterNo.ResponseId];

                    var deserialized = JsonPhotonSerializer.Deserialize(result);
                    log.Value       += "+ " + Info.Hub.HubName + "/" + Info.OperationName + ":" + deserialized + "\r\n";
                }
                catch (Exception ex)
                {
                    log.Value += "Send Error:" + ex.ToString() + "\r\n";
                }
            });
        }