【问题标题】:Cannot Move Divs in Blazor无法在 Blazor 中移动 div
【发布时间】:2022-01-02 11:38:55
【问题描述】:

我有一个带有以下 DIV 标记的 Blazor 服务器应用

<div class=mainScreen id="outerBox" style="width:@(TotalWidth)px;height:@(TotalHeight)px;">
    foreach(Device thisDevice in MyDevices)
    {
      <div class=column id="@MainDiv" style="width:@(thisDevice.Width)px;height:@(thisDevice.Height)px;left:@thisDevice.XCoordinate;top:@thisDevice.YCoordinate">
        Main Content Here...
      </div>
    }
    </div>

我尝试使用此页面中的代码示例 - https://blazor.tips/blazor-how-to-ready-window-dimensions/ 设置高度、宽度和 X/Y 坐标,但无论我将 Try... 块放置在何处,它都无法正常工作,只会引发未捕获的异常。

然后我转向更直接的 JS 调用:

 await Task.Run(async () =>
 {
  //Commenting out the OnInitializeAsync makes no difference but needs to be commented out when embedded
  //On the main component
  await this.OnInitializedAsync();
  string data = await JSRuntime.InvokeAsync<string>("getMyWindow", new object[] { });
  JObject offsets = (JObject)JsonConvert.DeserializeObject(data);
  TotalHeight = offsets.Value<int>("height");
  TotalHeight = offsets.Value<int>("width");
}

//In my JS file, the function looks as follows:
function getMyWindow() {
    var obj = {};
    obj.width = window.width;
    obj.height = window.height;
    return JSON.stringify(obj);
}

如果我直接在代码中进行此调用,则不会发生任何事情 - 即使注释掉了 OnInitializeAsync。

var result = SetDimensions().Result;

如果我将此方法放在 OnAfterRendor 方法中:

protected override void OnAfterRender(bool firstRender)
{
    if (firstRender)
    {
        if (!SetTheDivs)
            SetTheDivs = SetDimensions().Result;

        StateHasChanged();
    }
}
 
protected override void OnInitialized()
{
   base.OnInitialized();
   this.OnAfterRender(true);
}

在我终止该项目之前,一切都会挂起。从来没有任何错误,但是当我在高度或宽度语句上放置断点时,代码永远不会运行。

我什至在异步版本中添加了无济于事:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await SetDimensions();

        StateHasChanged();
    }
}

protected override async Task OnInitializedAsync()
{
    await this.OnAfterRenderAsync(true);
}

与所有挂起的结果相同。我完全不知道如何进行,我真的需要一些帮助!

为了清楚起见,导致挂起的是对 JS 的调用:

string data = await JSRuntime.InvokeAsync<string>("getMyWindow", new object[] { });

我添加了一些警报,但它们从未运行:

function getMyWindow() {
    var obj = {};
    alert("hi");
    obj.width = screen.width;
    obj.height = screen.height;
    alert("ho");
    return JSON.stringify(obj);
}

感谢您的宝贵时间!

顺便说一句 - 我确实将双重等待更改为 string data = JSRuntime.InvokeAsync&lt;string&gt;("getMyWindow", new object[] { }).Result;

更新:我将 JS 调用完全移到了等待之外,我得到了错误:

InvalidOperationException:此时无法发出 JavaScript 互操作调用。这是因为组件是静态渲染的。启用预渲染后,JavaScript 互操作调用只能在 OnAfterRenderAsync 生命周期方法期间执行。

在这种情况下,我实际上是从 OnAfterRenderAsync 方法调用该方法:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    await base.OnInitializedAsync();
    if (firstRender)
    {
        await SetDimensions();

        StateHasChanged();
    }
}

【问题讨论】:

    标签: javascript c# razor blazor


    【解决方案1】:

    不确定你想要什么...复制下面的代码并运行它,并告诉我们这是否是你想要得到的。

    Index.razor

    @page "/"
    
    <div class=mainScreen id="outerBox" style="width:@($"{TotalWidth}px");height:@($"{TotalHeight}px"); background-color: green; top:60px; position:absolute">
        @foreach (Device device in devices)
        {
        <div class=column style="width:@($"{device.Width}px");height:@($"{device.Height}px");margin-left:@($"{device.Left}px");margin-top:@($"{device.Top}px"); background-color:aliceblue">
           @device.Name: Main Content Here...
        </div>
        }
    </div>
    
    @code {
        private int TotalWidth = 520;
        private int TotalHeight = 530;
    
        private IList<Device> devices = Enumerable.Range(1, 5).Select(i => new Device { Name = $"Name {i}", Width = 520, Height = 100, Left = 0, Top = 5 }).ToList();
    
        public class Device
        {
            public string Name { get; set; }
            public int Width { get; set; }
            public int Height { get; set; }
            public int Left { get; set; }
            public int Top { get; set; }
        }
    }
    

    注意:OnInitialized{Async} 方法对是基类 ComponentBase. 的生命周期方法,它们在创建 Razor 组件时由 Blazor 框架自动调用。它们只执行一次。你可以覆盖它们,并添加你的逻辑,但你永远不应该从你的代码中手动调用它们。

    这个:

    protected override async Task OnInitializedAsync()
    {
        await this.OnAfterRenderAsync(true);
    } 
    

    这是错误的,绝对不能这样做。您不应该调用 OnAfterRender{Async}. 应该调用 OnAfterRender{Async} 的是 Blazor 框架,而不是开发人员。你能不能试着理解你的代码在做什么......

    尝试理解虽然 Razor 组件被定义为 C# 类,但它们是类的特殊情况,需要框架进行特殊处理...

    更新

    Ken Tola,我相信以下代码可以满足您的需求。它读取窗口对象的宽度和高度,将其传递给 Index 组件,然后重新定位您亲爱的 div。请注意,在应用程序重新定位 div 之前,我会检查宽度和高度的值,并确定 div 的尺寸。这当然是为了演示目的,您可以随意操作这些值...

    Index.razor

    @page "/"
        
    @implements IDisposable
    @inject IJSRuntime JSRuntime
    
        
    <div class=mainScreen id="outerBox" style="width:@($" {TotalWidth}px");height:@($"{TotalHeight}px"); background-color: green; top:60px; position:absolute">
        @foreach (Device device in devices)
        {
            <div class=column style="width:@($" {device.Width}px");height:@($"{device.Height}px");margin-left:@($"{device.Left}px");margin-top:@($"{device.Top}px"); background-color:aliceblue">
                @device.Name: Main Content Here...
            </div>
        }
    </div>
    
    @code
    {
    
        private DotNetObjectReference<BrowserService> objRef;
        private BrowserService BSS;
    
        private int TotalWidth; 
        private int TotalHeight; 
    
        private IList<Device> devices = Enumerable.Range(1, 5).Select(i => new Device { Name = $"Name {i}", Width = 520, Height = 100, Left = 0, Top = 5 }).ToList();
    
        public class Device
        {
            public string Name { get; set; }
            public int Width { get; set; }
            public int Height { get; set; }
            public int Left { get; set; }
            public int Top { get; set; }
        }
          
        protected override void OnInitialized()
        {
            BSS = new BrowserService();
    
            objRef = DotNetObjectReference.Create(BSS);
    
            BSS.Notify += OnNotify;
        }
        
        public void Dispose()
        {
            BSS.Notify -= OnNotify;
    
            objRef?.Dispose();
        }
    
        public async Task OnNotify()
        {
            // Note that the notifier only notify your component 
            // that data is ready, and that the dimensions are read
            // from a property. You can instead define event handler
            // that pass the data in the form of EventArgs... 
            TotalWidth = BSS.Dimension.Width >= 877 ? 520 : 550;
            TotalHeight = BSS.Dimension.Height >= 550 ? 800 : 1200;
        
            await InvokeAsync(() => StateHasChanged());
        }
    
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            // This code is excuted only once, in order to initialize
            // the JavaScript objects
            if (firstRender)
            {
                await JSRuntime.InvokeAsync<object> 
                      ("myJsFunctions.getDimensions", objRef);
                    
            }
        }
    
    }
    

    BrowserService.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text.Json;
    using System.Threading.Tasks;
    using Microsoft.JSInterop;
    
     public class BrowserService
        {
            public event Func<Task> Notify;
    #nullable enable
            public Dimension? Dimension { get; set; }
    #nullable disable
    
            [JSInvokableAttribute("GetDimensions")]
            public async Task GetDimensions(string dimension)
            {
                JsonSerializerOptions options = new(JsonSerializerDefaults.Web)
                {
                    WriteIndented = true
                };
                var _dimension = System.Text.Json.JsonSerializer.Deserialize(dimension, typeof(Dimension), options);
                Dimension = (Dimension)_dimension;
    
                if (Notify != null)
                {
                    await Notify?.Invoke();
                }
            }
        }
        public class Dimension
        {
            public int Width { get; set; }
            public int Height { get; set; }
    
        }
    }
    

    Startup.ConfigureServices

    services.AddScoped&lt;BrowserService&gt;();

    _Host.cshtml

    <script src="_framework/blazor.server.js"></script>
    
    <script type="text/javascript">
        window.myJsFunctions = {
    
            getDimensions: function (dotnetHelper) {
                var dimension = {
                     width: window.innerWidth,
                     height: window.innerHeight
                };
                var json = JSON.stringify(dimension);
    
                return dotnetHelper.invokeMethodAsync('GetDimensions', json);
            }
        };
    </script>
    

    注意:考虑在调整窗口大小时处理 div 元素的重定位。它应该是响应式的,对吧?不确定在您的情况下您是否可以使用媒体查询。无论如何,如您所见,我设计代码的方式考虑到您的 div 元素可能需要一次又一次地重新定位,因此它会不断(在调整大小时)通知您的 Index 组件尺寸变化.我想这值得提出一个新问题.....

    【讨论】:

    • 感谢您的回复!我只是试图获取窗口的高度和宽度,然后使用它们将 div 面板移动到屏幕上的正确位置。我可以通过单击按钮轻松地运行该方法,但是,即使包含 StateHasChanged,DIV 也不会移动,这不是我需要做的。我只是试图读取可用的总宽度/高度,并使用它在一切生成之前主动设置 X、Y、高度、宽度。
    • 你运行了我的代码吗?不,你没有。
    • Task.Run() 语句是我所有问题的根源!回到我上次编码时,它是在需要异步方法的方法中运行同步代码的首选路径——这似乎不仅仅是使用异步 void。太感谢了!最终代码直接从 OnAfterRenderAsync 运行就可以了。
    猜你喜欢
    • 2020-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-17
    • 2017-08-17
    • 1970-01-01
    相关资源
    最近更新 更多